util: Add stub unit tests for the call types in the m5 utility.
[gem5.git] / util / m5 / src / command / writefile.test.cc
1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met: redistributions of source code must retain the above copyright
7 * notice, this list of conditions and the following disclaimer;
8 * redistributions in binary form must reproduce the above copyright
9 * notice, this list of conditions and the following disclaimer in the
10 * documentation and/or other materials provided with the distribution;
11 * neither the name of the copyright holders nor the names of its
12 * contributors may be used to endorse or promote products derived from
13 * this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <gmock/gmock.h>
29 #include <gtest/gtest.h>
30 #include <sys/mman.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33
34 #include <cstdlib>
35 #include <cstring>
36 #include <sstream>
37 #include <string>
38
39 #include "args.hh"
40 #include "command.hh"
41 #include "dispatch_table.hh"
42
43 uint64_t test_total_written;
44 std::string test_host_file_name;
45
46 std::vector<uint8_t> test_written_data;
47 uint64_t test_max_buf_size;
48
49 uint64_t
50 test_m5_write_file(void *buffer, uint64_t len, uint64_t offset,
51 const char *filename)
52 {
53 if (test_max_buf_size && len > test_max_buf_size)
54 len = test_max_buf_size;
55
56 test_total_written += len;
57
58 if (test_host_file_name == "")
59 test_host_file_name = filename;
60 else
61 EXPECT_EQ(test_host_file_name, filename);
62
63 if (offset == 0)
64 test_written_data.clear();
65
66 size_t required_size = offset + len;
67 if (test_written_data.size() < required_size)
68 test_written_data.resize(required_size);
69
70 memcpy(test_written_data.data() + offset, buffer, len);
71
72 return len;
73 }
74
75 DispatchTable dt = { .m5_write_file = &test_m5_write_file };
76
77 std::string cout_output;
78
79 bool
80 run(std::initializer_list<std::string> arg_args)
81 {
82 test_total_written = 0;
83 test_host_file_name = "";
84 test_written_data.clear();
85
86 Args args(arg_args);
87
88 // Redirect cout into a stringstream.
89 std::stringstream buffer;
90 std::streambuf *orig = std::cout.rdbuf(buffer.rdbuf());
91
92 bool res = Command::run(dt, args);
93
94 // Capture the contents of the stringstream and restore cout.
95 cout_output = buffer.str();
96 std::cout.rdbuf(orig);
97
98 return res;
99 }
100
101 class TempFile
102 {
103 private:
104 size_t _size;
105 int fd;
106 std::string _path;
107 void *_buf;
108
109 public:
110 TempFile(size_t _size) : _size(_size)
111 {
112 // Generate a temporary filename.
113 char *tmp_name = strdup("/tmp/writefile.test.XXXXXXXX");
114 fd = mkstemp(tmp_name);
115 _path = tmp_name;
116 free(tmp_name);
117
118 // Make the file the appropriate length.
119 assert(!ftruncate(fd, _size));
120
121 // mmap the file.
122 _buf = mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
123 assert(_buf);
124
125 // Fill it with an incrementing 32 bit integers.
126
127 int chunk_size = sizeof(uint32_t);
128 size_t num_chunks = _size / chunk_size;
129 int leftovers = _size % chunk_size;
130
131 uint32_t *buf32 = (uint32_t *)_buf;
132 uint32_t val = 0;
133 for (size_t i = 0; i < num_chunks; i++)
134 *buf32++ = val++;
135 if (leftovers)
136 memcpy(buf32, &val, leftovers);
137
138 // Make sure our new contents are out there.
139 msync(_buf, _size, MS_SYNC | MS_INVALIDATE);
140 };
141
142 ~TempFile()
143 {
144 unlink(path().c_str());
145 close(fd);
146 }
147
148 size_t size() const { return _size; }
149 const std::string &path() const { return _path; }
150 const void *buf() const { return _buf; }
151
152 void
153 verify()
154 {
155 verify(path());
156 }
157
158 void
159 verify(const std::string &expected_path)
160 {
161 EXPECT_EQ(test_written_data.size(), size());
162 EXPECT_EQ(memcmp(test_written_data.data(), buf(), size()), 0);
163 EXPECT_EQ(test_host_file_name, expected_path);
164
165 std::ostringstream os;
166 os << "Opening \"" << path() << "\".";
167 EXPECT_THAT(cout_output, ::testing::HasSubstr(os.str()));
168 os.str("");
169 os << "Wrote " << size() << " bytes.";
170 EXPECT_THAT(cout_output, ::testing::HasSubstr(os.str()));
171 }
172 };
173
174 TEST(Writefile, NoArguments)
175 {
176 // Call with no arguments.
177 EXPECT_FALSE(run({"writefile"}));
178 EXPECT_EQ(test_total_written, 0);
179 }
180
181 TEST(Writefile, ThreeArguments)
182 {
183 // Call with no arguments.
184 EXPECT_FALSE(run({"writefile", "1", "2", "3"}));
185 EXPECT_EQ(test_total_written, 0);
186 }
187
188 TEST(Writefile, SmallFile)
189 {
190 // Write a small file.
191 TempFile tmp(16);
192 test_max_buf_size = 0;
193 EXPECT_TRUE(run({"writefile", tmp.path()}));
194 tmp.verify();
195 }
196
197 TEST(Writefile, SmallFileHostName)
198 {
199 // Write a small file with a different host file name.
200 TempFile tmp(16);
201 test_max_buf_size = 0;
202 std::string host_path = "/different/host/path";
203 EXPECT_TRUE(run({"writefile", tmp.path(), host_path}));
204 tmp.verify(host_path);
205 }
206
207 TEST(Writefile, MultipleChunks)
208 {
209 // Write a file which will need to be split into multiple whole chunks.
210 TempFile tmp(256 * 1024 * 4);
211 test_max_buf_size = 0;
212 EXPECT_TRUE(run({"writefile", tmp.path()}));
213 tmp.verify();
214 }
215
216 TEST(Writefile, MultipleAndPartialChunks)
217 {
218 // Write a file which will be split into some whole and one partial chunk.
219 TempFile tmp(256 * 1024 * 2 + 256);
220 test_max_buf_size = 0;
221 EXPECT_TRUE(run({"writefile", tmp.path()}));
222 tmp.verify();
223 }
224
225 TEST(Writefile, OddSizedChunks)
226 {
227 // Write a file in chunks that aren't nicely aligned.
228 TempFile tmp(256 * 1024);
229 test_max_buf_size = 13;
230 EXPECT_TRUE(run({"writefile", tmp.path()}));
231 tmp.verify();
232 }
233
234 TEST(Writefile, CappedWriteSize)
235 {
236 // Write a file, accepting less than the requested amount of data.
237 TempFile tmp(256 * 1024 * 2 + 256);
238 test_max_buf_size = 256;
239 EXPECT_TRUE(run({"writefile", tmp.path()}));
240 tmp.verify();
241 }
242
243 TEST(WritefileDeathTest, BadFile)
244 {
245 EXPECT_EXIT(run({"writefile", "this is not a valid path#$#$://\\\\"}),
246 ::testing::ExitedWithCode(2), "Error opening ");
247 }