util: Add a "writefile" unit test to the m5 utility.
authorGabe Black <gabeblack@google.com>
Wed, 8 Apr 2020 10:50:20 +0000 (03:50 -0700)
committerGabe Black <gabeblack@google.com>
Thu, 30 Jul 2020 01:01:54 +0000 (01:01 +0000)
Change-Id: Ic0e8d5fbbd5b6d6b57f674cef6460f94206a5872
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/27628
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Maintainer: Gabe Black <gabeblack@google.com>
Tested-by: Gem5 Cloud Project GCB service account <345032938727@cloudbuild.gserviceaccount.com>
util/m5/src/command/SConscript.native
util/m5/src/command/writefile.test.cc [new file with mode: 0644]

index 7d23b2a06db5bbba5cf0c305aee3368b2f3a37bb..fc3e975ae6af930b976a4aaaf90156b531de9d02 100644 (file)
@@ -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 (file)
index 0000000..a1adf5c
--- /dev/null
@@ -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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <sstream>
+#include <string>
+
+#include "args.hh"
+#include "command.hh"
+#include "dispatch_table.hh"
+
+uint64_t test_total_written;
+std::string test_host_file_name;
+
+std::vector<uint8_t> 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<std::string> 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 ");
+}