From deb48638eac79ca3d4f1f11ba7b8de8fd5fdf848 Mon Sep 17 00:00:00 2001 From: Gabe Black Date: Wed, 8 Apr 2020 01:55:58 -0700 Subject: [PATCH] util: Add a unit test for the m5 utility's "readfile" command. This feeds a fake file to the readfile command which is just a sequence of incrementing 32 bit values. The incrementing values make sure that the right region of the input file is being read at the right position, and the relatively small size means there shouldn't be tons of zeroes everywhere which can't be distinguished from each other. Change-Id: I4286b1f92684f127c4885c29192c6c5244a61855 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/27608 Maintainer: Gabe Black Tested-by: kokoro Tested-by: Gem5 Cloud Project GCB service account <345032938727@cloudbuild.gserviceaccount.com> Reviewed-by: Jason Lowe-Power --- util/m5/src/command/SConscript.native | 1 + util/m5/src/command/readfile.test.cc | 208 ++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 util/m5/src/command/readfile.test.cc diff --git a/util/m5/src/command/SConscript.native b/util/m5/src/command/SConscript.native index 10e45d21c..7d23b2a06 100644 --- a/util/m5/src/command/SConscript.native +++ b/util/m5/src/command/SConscript.native @@ -34,6 +34,7 @@ command_tests = ( 'fail', 'initparam', 'loadsymbol', + 'readfile', 'resetstats', 'sum', ) diff --git a/util/m5/src/command/readfile.test.cc b/util/m5/src/command/readfile.test.cc new file mode 100644 index 000000000..d5ffe1d01 --- /dev/null +++ b/util/m5/src/command/readfile.test.cc @@ -0,0 +1,208 @@ +/* + * 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 "args.hh" +#include "command.hh" +#include "dispatch_table.hh" + +uint64_t test_read_file_size; +uint64_t test_max_buf_size; + +uint64_t test_total_read; + +uint64_t +test_m5_read_file(void *buffer, uint64_t len, uint64_t offset) +{ + // The "file" we're reading is just a series of incrementing 32 bit + // integers. + + // If the buffer is entirely past the end of our "file", return 0. + if (offset >= test_read_file_size) + return 0; + + // If the buffer extends beyond our "file" truncate it. + if (offset + len > test_read_file_size) + len = test_read_file_size - offset; + + // If more data was requested than we want to send at once, truncate len. + if (test_max_buf_size && len > test_max_buf_size) + len = test_max_buf_size; + + int chunk_size = sizeof(uint32_t); + + // How much of len is still unaccounted for. + uint64_t remaining = len; + + // How much overlaps with the preceeding chunk? + int at_start = chunk_size - (offset % chunk_size); + // If we don't even cover the entire previous chunk... + if (at_start > len) + at_start = len; + remaining -= at_start; + + // How much overlaps with the following chunk? + int at_end = remaining % chunk_size; + remaining -= at_end; + + // The number of chunks are the number we cover fully, plus one for each + // end were we partially overlap. + uint64_t num_chunks = remaining / chunk_size + + (at_start ? 1 : 0) + (at_end ? 1 : 0); + + // Build this part of the file. + uint32_t *chunks = new uint32_t [num_chunks]; + + uint32_t chunk_idx = offset / chunk_size; + for (uint64_t i = 0; i < num_chunks; i++) + chunks[i] = chunk_idx++; + + // Copy out to the requested buffer. + memcpy(buffer, ((uint8_t *)chunks) + (chunk_size - at_start), len); + + // Clean up. + delete [] chunks; + + test_total_read += len; + return len; +} + +DispatchTable dt = { .m5_read_file = &test_m5_read_file }; + +std::string cout_output; + +bool +run(std::initializer_list arg_args, bool bad_file=false) +{ + test_total_read = 0; + + Args args(arg_args); + + // Redirect cout into a stringstream. + std::stringstream buffer; + std::streambuf *orig = std::cout.rdbuf(buffer.rdbuf()); + + // Simulate a problem writing to cout. + if (bad_file) + std::cout.setstate(std::cout.badbit); + + bool res = Command::run(dt, args); + + if (bad_file) + std::cout.clear(); + + // Capture the contents of the stringstream and restore cout. + cout_output = buffer.str(); + std::cout.rdbuf(orig); + + return res; +} + +void +test_verify_data() +{ + EXPECT_EQ(test_total_read, test_read_file_size); + EXPECT_EQ(cout_output.size(), test_read_file_size); + + auto *data32 = (const uint32_t *)cout_output.data(); + uint64_t len = cout_output.size(); + + int chunk_size = sizeof(uint32_t); + + uint64_t num_chunks = len / chunk_size; + int leftovers = len % chunk_size; + + uint32_t chunk_idx; + for (chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) + EXPECT_EQ(*data32++, chunk_idx); + + if (leftovers) + EXPECT_EQ(memcmp(&chunk_idx, data32, leftovers), 0); +} + +TEST(Readfile, OneArgument) +{ + // Call with an argument. + EXPECT_FALSE(run({"readfile", "foo"})); + EXPECT_EQ(test_total_read, 0); +} + +TEST(Readfile, SmallFile) +{ + // Read a small "file". + test_read_file_size = 16; + test_max_buf_size = 0; + EXPECT_TRUE(run({"readfile"})); + test_verify_data(); +} + +TEST(Readfile, MultipleChunks) +{ + // Read a "file" which will need to be split into multiple whole chunks. + test_read_file_size = 256 * 1024 * 4; + test_max_buf_size = 0; + EXPECT_TRUE(run({"readfile"})); + test_verify_data(); +} + +TEST(Readfile, MultipleAndPartialChunks) +{ + // Read a "file" which will be split into some whole and one partial chunk. + test_read_file_size = 256 * 1024 * 2 + 256; + test_max_buf_size = 0; + EXPECT_TRUE(run({"readfile"})); + test_verify_data(); +} + +TEST(Readfile, OddSizedChunks) +{ + // Read a "file" in chunks that aren't nicely aligned. + test_read_file_size = 256 * 1024; + test_max_buf_size = 13; + EXPECT_TRUE(run({"readfile"})); + test_verify_data(); +} + +TEST(Readfile, CappedReadSize) +{ + // Read a "file", returning less than the requested amount of data. + test_read_file_size = 256 * 1024 * 2 + 256; + test_max_buf_size = 256; + EXPECT_TRUE(run({"readfile"})); + test_verify_data(); +} + +TEST(ReadfileDeathTest, BadFile) +{ + test_read_file_size = 16; + test_max_buf_size = 0; + EXPECT_EXIT(run({"readfile"}, true), ::testing::ExitedWithCode(2), + "Failed to write file"); +} -- 2.30.2