//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/Support/VirtualOutputFile.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::vfs; namespace { struct MockOutputFileData { int Kept = 0; int Discarded = 0; int Handled = 0; unique_function Keeper; unique_function Discarder; void handler(Error E) { consumeError(std::move(E)); ++Handled; } unique_function getHandler() { return [this](Error E) { handler(std::move(E)); }; } SmallString<128> V; std::optional VOS; raw_pwrite_stream *OS = nullptr; MockOutputFileData() : VOS(std::in_place, V), OS(&*VOS) {} MockOutputFileData(raw_pwrite_stream &OS) : OS(&OS) {} }; struct MockOutputFile final : public OutputFileImpl { Error keep() override { ++Data.Kept; if (Data.Keeper) return Data.Keeper(); return Error::success(); } Error discard() override { ++Data.Discarded; if (Data.Discarder) return Data.Discarder(); return Error::success(); } raw_pwrite_stream &getOS() override { if (!Data.OS) report_fatal_error("missing stream in MockOutputFile::getOS"); return *Data.OS; } MockOutputFile(MockOutputFileData &Data) : Data(Data) {} MockOutputFileData &Data; }; static std::unique_ptr createMockOutput(MockOutputFileData &Data) { return std::make_unique(Data); } static Error createCustomError() { return createStringError(inconvertibleErrorCode(), "custom error"); } TEST(VirtualOutputFileTest, construct) { OutputFile F; EXPECT_EQ("", F.getPath()); EXPECT_FALSE(F); EXPECT_FALSE(F.isOpen()); #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) EXPECT_DEATH(F.getOS(), "Expected open output stream"); #endif } #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) TEST(VirtualOutputFileTest, constructNull) { EXPECT_DEATH(OutputFile("some/file/path", nullptr), "Expected open output file"); } #endif TEST(VirtualOutputFileTest, destroy) { MockOutputFileData Data; StringRef FilePath = "some/file/path"; // Check behaviour when destroying, first without a handler and then with // one. The handler shouldn't be called. std::optional F(std::in_place, FilePath, createMockOutput(Data)); EXPECT_TRUE(F->isOpen()); EXPECT_EQ(FilePath, F->getPath()); EXPECT_EQ(Data.OS, &F->getOS()); #if GTEST_HAS_DEATH_TEST EXPECT_DEATH(F.reset(), "output not closed"); #endif F->discardOnDestroy(Data.getHandler()); EXPECT_EQ(0, Data.Discarded); EXPECT_EQ(0, Data.Handled); F.reset(); EXPECT_EQ(1, Data.Discarded); EXPECT_EQ(0, Data.Handled); // Try again when discard returns an error. This time the handler should be // called. Data.Discarder = createCustomError; F.emplace("some/file/path", createMockOutput(Data)); F->discardOnDestroy(Data.getHandler()); F.reset(); EXPECT_EQ(2, Data.Discarded); EXPECT_EQ(1, Data.Handled); } TEST(VirtualOutputFileTest, destroyProxy) { MockOutputFileData Data; std::optional F(std::in_place, "some/file/path", createMockOutput(Data)); F->discardOnDestroy(Data.getHandler()); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F->createProxy().moveInto(Proxy), Succeeded()); F.reset(); #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) EXPECT_DEATH(*Proxy << "data", "use after reset"); #endif Proxy.reset(); } TEST(VirtualOutputFileTest, discard) { StringRef Content = "some data"; MockOutputFileData Data; { OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F << Content; EXPECT_EQ(Content, Data.V); EXPECT_THAT_ERROR(F.discard(), Succeeded()); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); #if GTEST_HAS_DEATH_TEST EXPECT_DEATH(consumeError(F.keep()), "some/file/path: output already closed"); EXPECT_DEATH(consumeError(F.discard()), "some/file/path: output already closed"); #endif } EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); } TEST(VirtualOutputFileTest, discardError) { StringRef Content = "some data"; MockOutputFileData Data; Data.Discarder = createCustomError; { OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F << Content; EXPECT_EQ(Content, Data.V); EXPECT_THAT_ERROR(F.discard(), FailedWithMessage("custom error")); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); EXPECT_EQ(0, Data.Handled); } EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); EXPECT_EQ(0, Data.Handled); } TEST(VirtualOutputFileTest, discardProxy) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ(Content, Data.V); EXPECT_THAT_ERROR(F.discard(), Succeeded()); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); } TEST(VirtualOutputFileTest, discardProxyFlush) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F.getOS().SetBufferSize(Content.size() * 2); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ("", Data.V); EXPECT_THAT_ERROR(F.discard(), Succeeded()); EXPECT_EQ(Content, Data.V); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(0, Data.Kept); EXPECT_EQ(1, Data.Discarded); } TEST(VirtualOutputFileTest, keep) { StringRef Content = "some data"; MockOutputFileData Data; { OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F << Content; EXPECT_EQ(Content, Data.V); EXPECT_THAT_ERROR(F.keep(), Succeeded()); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); #if GTEST_HAS_DEATH_TEST EXPECT_DEATH(consumeError(F.keep()), "some/file/path: output already closed"); EXPECT_DEATH(consumeError(F.discard()), "some/file/path: output already closed"); #endif } EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); } TEST(VirtualOutputFileTest, keepError) { StringRef Content = "some data"; MockOutputFileData Data; Data.Keeper = createCustomError; { OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F << Content; EXPECT_EQ(Content, Data.V); EXPECT_THAT_ERROR(F.keep(), FailedWithMessage("custom error")); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); EXPECT_EQ(0, Data.Handled); } EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); EXPECT_EQ(0, Data.Handled); } TEST(VirtualOutputFileTest, keepProxy) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ(Content, Data.V); Proxy.reset(); EXPECT_THAT_ERROR(F.keep(), Succeeded()); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); } #if GTEST_HAS_DEATH_TEST TEST(VirtualOutputFileTest, keepProxyStillOpen) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ(Content, Data.V); EXPECT_DEATH(consumeError(F.keep()), "some/file/path: output has open proxy"); } #endif TEST(VirtualOutputFileTest, keepProxyFlush) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); F.getOS().SetBufferSize(Content.size() * 2); std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ("", Data.V); Proxy.reset(); EXPECT_THAT_ERROR(F.keep(), Succeeded()); EXPECT_EQ(Content, Data.V); EXPECT_FALSE(F.isOpen()); EXPECT_EQ(1, Data.Kept); EXPECT_EQ(0, Data.Discarded); } TEST(VirtualOutputFileTest, TwoProxies) { StringRef Content = "some data"; MockOutputFileData Data; OutputFile F("some/file/path", createMockOutput(Data)); F.discardOnDestroy(Data.getHandler()); // Can't have two open proxies at once. { std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); EXPECT_THAT_ERROR( F.createProxy().takeError(), FailedWithMessage("some/file/path: output has open proxy")); } EXPECT_EQ(0, Data.Kept); EXPECT_EQ(0, Data.Discarded); // A second proxy after the first closes should work... { std::unique_ptr Proxy; EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded()); *Proxy << Content; EXPECT_EQ(Content, Data.V); } } } // end namespace