From a058d66a65def786d0b92fba7c1a4333f7734eb7 Mon Sep 17 00:00:00 2001 From: Gabe Black Date: Wed, 8 Apr 2020 03:50:20 -0700 Subject: [PATCH] util: Add a "writefile" unit test to the m5 utility. Change-Id: Ic0e8d5fbbd5b6d6b57f674cef6460f94206a5872 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/27628 Reviewed-by: Jason Lowe-Power Maintainer: Gabe Black Tested-by: Gem5 Cloud Project GCB service account <345032938727@cloudbuild.gserviceaccount.com> --- util/m5/src/command/SConscript.native | 1 + util/m5/src/command/writefile.test.cc | 247 ++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 util/m5/src/command/writefile.test.cc diff --git a/util/m5/src/command/SConscript.native b/util/m5/src/command/SConscript.native index 7d23b2a06..fc3e975ae 100644 --- a/util/m5/src/command/SConscript.native +++ b/util/m5/src/command/SConscript.native @@ -37,6 +37,7 @@ command_tests = ( 'readfile', 'resetstats', 'sum', + 'writefile', ) Return('command_tests') diff --git a/util/m5/src/command/writefile.test.cc b/util/m5/src/command/writefile.test.cc new file mode 100644 index 000000000..a1adf5c4f --- /dev/null +++ b/util/m5/src/command/writefile.test.cc @@ -0,0 +1,247 @@ +/* + * Copyright 2020 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "args.hh" +#include "command.hh" +#include "dispatch_table.hh" + +uint64_t test_total_written; +std::string test_host_file_name; + +std::vector test_written_data; +uint64_t test_max_buf_size; + +uint64_t +test_m5_write_file(void *buffer, uint64_t len, uint64_t offset, + const char *filename) +{ + if (test_max_buf_size && len > test_max_buf_size) + len = test_max_buf_size; + + test_total_written += len; + + if (test_host_file_name == "") + test_host_file_name = filename; + else + EXPECT_EQ(test_host_file_name, filename); + + if (offset == 0) + test_written_data.clear(); + + size_t required_size = offset + len; + if (test_written_data.size() < required_size) + test_written_data.resize(required_size); + + memcpy(test_written_data.data() + offset, buffer, len); + + return len; +} + +DispatchTable dt = { .m5_write_file = &test_m5_write_file }; + +std::string cout_output; + +bool +run(std::initializer_list arg_args) +{ + test_total_written = 0; + test_host_file_name = ""; + test_written_data.clear(); + + Args args(arg_args); + + // Redirect cout into a stringstream. + std::stringstream buffer; + std::streambuf *orig = std::cout.rdbuf(buffer.rdbuf()); + + bool res = Command::run(dt, args); + + // Capture the contents of the stringstream and restore cout. + cout_output = buffer.str(); + std::cout.rdbuf(orig); + + return res; +} + +class TempFile +{ + private: + size_t _size; + int fd; + std::string _path; + void *_buf; + + public: + TempFile(size_t _size) : _size(_size) + { + // Generate a temporary filename. + char *tmp_name = strdup("/tmp/writefile.test.XXXXXXXX"); + fd = mkstemp(tmp_name); + _path = tmp_name; + free(tmp_name); + + // Make the file the appropriate length. + assert(!ftruncate(fd, _size)); + + // mmap the file. + _buf = mmap(nullptr, _size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + assert(_buf); + + // Fill it with an incrementing 32 bit integers. + + int chunk_size = sizeof(uint32_t); + size_t num_chunks = _size / chunk_size; + int leftovers = _size % chunk_size; + + uint32_t *buf32 = (uint32_t *)_buf; + uint32_t val = 0; + for (size_t i = 0; i < num_chunks; i++) + *buf32++ = val++; + if (leftovers) + memcpy(buf32, &val, leftovers); + + // Make sure our new contents are out there. + msync(_buf, _size, MS_SYNC | MS_INVALIDATE); + }; + + ~TempFile() + { + unlink(path().c_str()); + close(fd); + } + + size_t size() const { return _size; } + const std::string &path() const { return _path; } + const void *buf() const { return _buf; } + + void + verify() + { + verify(path()); + } + + void + verify(const std::string &expected_path) + { + EXPECT_EQ(test_written_data.size(), size()); + EXPECT_EQ(memcmp(test_written_data.data(), buf(), size()), 0); + EXPECT_EQ(test_host_file_name, expected_path); + + std::ostringstream os; + os << "Opening \"" << path() << "\"."; + EXPECT_THAT(cout_output, ::testing::HasSubstr(os.str())); + os.str(""); + os << "Wrote " << size() << " bytes."; + EXPECT_THAT(cout_output, ::testing::HasSubstr(os.str())); + } +}; + +TEST(Writefile, NoArguments) +{ + // Call with no arguments. + EXPECT_FALSE(run({"writefile"})); + EXPECT_EQ(test_total_written, 0); +} + +TEST(Writefile, ThreeArguments) +{ + // Call with no arguments. + EXPECT_FALSE(run({"writefile", "1", "2", "3"})); + EXPECT_EQ(test_total_written, 0); +} + +TEST(Writefile, SmallFile) +{ + // Write a small file. + TempFile tmp(16); + test_max_buf_size = 0; + EXPECT_TRUE(run({"writefile", tmp.path()})); + tmp.verify(); +} + +TEST(Writefile, SmallFileHostName) +{ + // Write a small file with a different host file name. + TempFile tmp(16); + test_max_buf_size = 0; + std::string host_path = "/different/host/path"; + EXPECT_TRUE(run({"writefile", tmp.path(), host_path})); + tmp.verify(host_path); +} + +TEST(Writefile, MultipleChunks) +{ + // Write a file which will need to be split into multiple whole chunks. + TempFile tmp(256 * 1024 * 4); + test_max_buf_size = 0; + EXPECT_TRUE(run({"writefile", tmp.path()})); + tmp.verify(); +} + +TEST(Writefile, MultipleAndPartialChunks) +{ + // Write a file which will be split into some whole and one partial chunk. + TempFile tmp(256 * 1024 * 2 + 256); + test_max_buf_size = 0; + EXPECT_TRUE(run({"writefile", tmp.path()})); + tmp.verify(); +} + +TEST(Writefile, OddSizedChunks) +{ + // Write a file in chunks that aren't nicely aligned. + TempFile tmp(256 * 1024); + test_max_buf_size = 13; + EXPECT_TRUE(run({"writefile", tmp.path()})); + tmp.verify(); +} + +TEST(Writefile, CappedWriteSize) +{ + // Write a file, accepting less than the requested amount of data. + TempFile tmp(256 * 1024 * 2 + 256); + test_max_buf_size = 256; + EXPECT_TRUE(run({"writefile", tmp.path()})); + tmp.verify(); +} + +TEST(WritefileDeathTest, BadFile) +{ + EXPECT_EXIT(run({"writefile", "this is not a valid path#$#$://\\\\"}), + ::testing::ExitedWithCode(2), "Error opening "); +} -- 2.30.2