// Copyright 2022 The Centipede Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "./common/blob_file.h" #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/status/status.h" #include "./common/defs.h" #include "./common/test_util.h" namespace fuzztest::internal { namespace { std::string TempFilePath() { return TempDir("test").GetFilePath("blob_file"); } // We may have more than one BlobFile factory. // Need to test every factory the same way. class BlobFile : public testing::TestWithParam {}; // Tests correct way of using a BlobFile, and that files written in both formats // can be appropriately detected and read. void TestOneBlobFile(std::unique_ptr (*ReaderFactory)(), std::unique_ptr (*WriterFactory)(bool), bool riegeli) { ByteArray input1{1, 2, 3}; ByteArray input2{4, 5}; ByteArray input3{6, 7, 8, 9}; ByteArray input4{10, 11}; const auto path = TempFilePath(); ByteSpan blob; // Append two blobs to a file. { auto appender = WriterFactory(riegeli); EXPECT_OK(appender->Open(path, "a")); EXPECT_OK(appender->Write(input1)); EXPECT_OK(appender->Write(input2)); EXPECT_OK(appender->Close()); } // Read the blobs back. { auto reader = ReaderFactory(); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input1, blob); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input2, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); } // Append one more blob to the same file. { auto appender = WriterFactory(riegeli); EXPECT_OK(appender->Open(path, "a")); EXPECT_OK(appender->Write(input3)); EXPECT_OK(appender->Close()); } // Re-read the file, expect to see all 3 blobs. { auto reader = ReaderFactory(); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input1, blob); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input2, blob); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input3, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); } // Overwrite the contents of the file by a new blob. { auto appender = WriterFactory(riegeli); EXPECT_OK(appender->Open(path, "w")); EXPECT_OK(appender->Write(input4)); EXPECT_OK(appender->Close()); } // Re-read the file, expect to see the single new blob. { auto reader = ReaderFactory(); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(input4, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); } } TEST_P(BlobFile, DefaultTest) { TestOneBlobFile(&DefaultBlobFileReaderFactory, &DefaultBlobFileWriterFactory, GetParam()); } // An Open() failure should not interfere with future proper functioning. TEST_P(BlobFile, AfterFailedOpenTest) { auto reader = DefaultBlobFileReaderFactory(); auto appender = DefaultBlobFileWriterFactory(GetParam()); const std::string invalid_path = "/DOES/NOT/EXIST"; const std::string path = TempFilePath(); ByteArray input{1, 2, 3}; // Open failure of writer due to file that cannot be created. ASSERT_FALSE(appender->Open(invalid_path, "a").ok()); // Follow that with opening and writing to a file that is expected to work. ASSERT_OK(appender->Open(path, "a")); ASSERT_OK(appender->Write(input)); ASSERT_OK(appender->Close()); // Open failure of reader due to non-existent file. ASSERT_FALSE(reader->Open(invalid_path).ok()); ByteSpan blob; // Follow that with reading the already written file and check that contents // are as expected. ASSERT_OK(reader->Open(path)); ASSERT_OK(reader->Read(blob)); EXPECT_EQ(input, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); } TEST_P(BlobFile, CloseReaderAfterFileRemovalTest) { auto appender = DefaultBlobFileWriterFactory(GetParam()); const std::string path = TempFilePath(); ByteArray input{1}; ASSERT_OK(appender->Open(path, "a")); ASSERT_OK(appender->Write(input)); ASSERT_OK(appender->Close()); auto reader = DefaultBlobFileReaderFactory(); ByteSpan blob; ASSERT_OK(reader->Open(path)); ASSERT_OK(reader->Read(blob)); TempFilePath(); // Delete the file at `path` EXPECT_OK(reader->Close()); } // Tests incorrect ways of using a BlobFileReader/BlobFileWriter. void TestIncorrectUsage(std::unique_ptr (*ReaderFactory)(), std::unique_ptr (*WriterFactory)(bool), bool riegeli) { const std::string invalid_path = "/DOES/NOT/EXIST"; const auto path = TempFilePath(); auto reader = ReaderFactory(); auto appender = WriterFactory(riegeli); // Open invalid file path. EXPECT_FALSE(reader->Open(invalid_path).ok()); EXPECT_FALSE(appender->Open(invalid_path, "a").ok()); ByteSpan blob; // Write() on objects that are not in a successfuly open state. EXPECT_FALSE(appender->Write(blob).ok()); EXPECT_OK(appender->Open(path, "a")); EXPECT_OK(appender->Close()); EXPECT_FALSE(appender->Write(blob).ok()); // Read() on objects that are not in a successfully open state. EXPECT_FALSE(reader->Read(blob).ok()); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Close()); EXPECT_FALSE(reader->Read(blob).ok()); } TEST_P(BlobFile, IncorrectUsage) { TestIncorrectUsage(&DefaultBlobFileReaderFactory, &DefaultBlobFileWriterFactory, GetParam()); } #ifndef CENTIPEDE_DISABLE_RIEGELI INSTANTIATE_TEST_SUITE_P(BlobFileTests, BlobFile, ::testing::Bool()); #else INSTANTIATE_TEST_SUITE_P(BlobFileTests, BlobFile, ::testing::Values(false)); #endif // CENTIPEDE_DISABLE_RIEGELI class ReadMultipleFiles : public testing::TestWithParam> {}; // Single reader object can be used to read multiple files, in the same or // different formats. TEST_P(ReadMultipleFiles, SingleObjectMultipleFormats) { auto reader = DefaultBlobFileReaderFactory(); auto [first_riegeli, second_riegeli] = GetParam(); const auto path = TempFilePath(); ByteArray file1_blob1{1, 2, 3, 4, 5}; ByteArray file2_blob1{6, 7}; ByteArray file2_blob2{8, 9}; ByteSpan blob; // Write in first format and read. auto writer = DefaultBlobFileWriterFactory(first_riegeli); EXPECT_OK(writer->Open(path, "w")); EXPECT_OK(writer->Write(file1_blob1)); EXPECT_OK(writer->Close()); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(file1_blob1, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); // Write in second format and read. writer = DefaultBlobFileWriterFactory(second_riegeli); EXPECT_OK(writer->Open(path, "w")); EXPECT_OK(writer->Write(file2_blob1)); EXPECT_OK(writer->Write(file2_blob2)); EXPECT_OK(writer->Close()); EXPECT_OK(reader->Open(path)); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(file2_blob1, blob); EXPECT_OK(reader->Read(blob)); EXPECT_EQ(file2_blob2, blob); EXPECT_EQ(reader->Read(blob), absl::OutOfRangeError("no more blobs")); EXPECT_OK(reader->Close()); } #ifndef CENTIPEDE_DISABLE_RIEGELI INSTANTIATE_TEST_SUITE_P(DefaultBlobFileReaderTests, ReadMultipleFiles, ::testing::Combine(::testing::Bool(), ::testing::Bool())); #else INSTANTIATE_TEST_SUITE_P(DefaultBlobFileReaderTests, ReadMultipleFiles, ::testing::Values(std::tuple{false, false})); #endif // CENTIPEDE_DISABLE_RIEGELI #ifdef CENTIPEDE_DISABLE_RIEGELI TEST(WriterFactoryDeathTest, FailWhenBuiltWithoutRiegeli) { ASSERT_DEATH(DefaultBlobFileWriterFactory(true), ""); } #endif // CENTIPEDE_DISABLE_RIEGELI void Append(ByteArray &to, const ByteArray &from) { to.insert(to.end(), from.begin(), from.end()); } TEST(UtilTest, AppendFile) { ByteArray packed; ByteArray a{1, 2, 3}; ByteArray b{3, 4, 5}; ByteArray c{111, 112, 113, 114, 115}; Append(packed, PackBytesForAppendFile(a)); Append(packed, PackBytesForAppendFile(b)); Append(packed, PackBytesForAppendFile(c)); std::vector unpacked; UnpackBytesFromAppendFile(packed, &unpacked); EXPECT_EQ(a, unpacked[0]); EXPECT_EQ(b, unpacked[1]); EXPECT_EQ(c, unpacked[2]); } } // namespace } // namespace fuzztest::internal