| /* |
| * 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 <gtest/gtest.h> |
| |
| #include <cstring> |
| #include <sstream> |
| |
| #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. |
| std::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<std::string> 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"); |
| } |