343 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Support/VirtualOutputFile.h"10#include "llvm/Testing/Support/Error.h"11#include "gtest/gtest.h"12 13using namespace llvm;14using namespace llvm::vfs;15 16namespace {17 18struct MockOutputFileData {19 int Kept = 0;20 int Discarded = 0;21 int Handled = 0;22 unique_function<Error()> Keeper;23 unique_function<Error()> Discarder;24 25 void handler(Error E) {26 consumeError(std::move(E));27 ++Handled;28 }29 unique_function<void(Error)> getHandler() {30 return [this](Error E) { handler(std::move(E)); };31 }32 33 SmallString<128> V;34 std::optional<raw_svector_ostream> VOS;35 raw_pwrite_stream *OS = nullptr;36 37 MockOutputFileData() : VOS(std::in_place, V), OS(&*VOS) {}38 MockOutputFileData(raw_pwrite_stream &OS) : OS(&OS) {}39};40 41struct MockOutputFile final : public OutputFileImpl {42 Error keep() override {43 ++Data.Kept;44 if (Data.Keeper)45 return Data.Keeper();46 return Error::success();47 }48 49 Error discard() override {50 ++Data.Discarded;51 if (Data.Discarder)52 return Data.Discarder();53 return Error::success();54 }55 56 raw_pwrite_stream &getOS() override {57 if (!Data.OS)58 report_fatal_error("missing stream in MockOutputFile::getOS");59 return *Data.OS;60 }61 62 MockOutputFile(MockOutputFileData &Data) : Data(Data) {}63 MockOutputFileData &Data;64};65 66static std::unique_ptr<MockOutputFile>67createMockOutput(MockOutputFileData &Data) {68 return std::make_unique<MockOutputFile>(Data);69}70 71static Error createCustomError() {72 return createStringError(inconvertibleErrorCode(), "custom error");73}74 75TEST(VirtualOutputFileTest, construct) {76 OutputFile F;77 EXPECT_EQ("", F.getPath());78 EXPECT_FALSE(F);79 EXPECT_FALSE(F.isOpen());80 81#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)82 EXPECT_DEATH(F.getOS(), "Expected open output stream");83#endif84}85 86#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)87TEST(VirtualOutputFileTest, constructNull) {88 EXPECT_DEATH(OutputFile("some/file/path", nullptr),89 "Expected open output file");90}91#endif92 93TEST(VirtualOutputFileTest, destroy) {94 MockOutputFileData Data;95 StringRef FilePath = "some/file/path";96 97 // Check behaviour when destroying, first without a handler and then with98 // one. The handler shouldn't be called.99 std::optional<OutputFile> F(std::in_place, FilePath, createMockOutput(Data));100 EXPECT_TRUE(F->isOpen());101 EXPECT_EQ(FilePath, F->getPath());102 EXPECT_EQ(Data.OS, &F->getOS());103#if GTEST_HAS_DEATH_TEST104 EXPECT_DEATH(F.reset(), "output not closed");105#endif106 F->discardOnDestroy(Data.getHandler());107 EXPECT_EQ(0, Data.Discarded);108 EXPECT_EQ(0, Data.Handled);109 F.reset();110 EXPECT_EQ(1, Data.Discarded);111 EXPECT_EQ(0, Data.Handled);112 113 // Try again when discard returns an error. This time the handler should be114 // called.115 Data.Discarder = createCustomError;116 F.emplace("some/file/path", createMockOutput(Data));117 F->discardOnDestroy(Data.getHandler());118 F.reset();119 EXPECT_EQ(2, Data.Discarded);120 EXPECT_EQ(1, Data.Handled);121}122 123TEST(VirtualOutputFileTest, destroyProxy) {124 MockOutputFileData Data;125 126 std::optional<OutputFile> F(std::in_place, "some/file/path",127 createMockOutput(Data));128 F->discardOnDestroy(Data.getHandler());129 std::unique_ptr<raw_pwrite_stream> Proxy;130 EXPECT_THAT_ERROR(F->createProxy().moveInto(Proxy), Succeeded());131 F.reset();132#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)133 EXPECT_DEATH(*Proxy << "data", "use after reset");134#endif135 Proxy.reset();136}137 138TEST(VirtualOutputFileTest, discard) {139 StringRef Content = "some data";140 MockOutputFileData Data;141 {142 OutputFile F("some/file/path", createMockOutput(Data));143 F.discardOnDestroy(Data.getHandler());144 F << Content;145 EXPECT_EQ(Content, Data.V);146 147 EXPECT_THAT_ERROR(F.discard(), Succeeded());148 EXPECT_FALSE(F.isOpen());149 EXPECT_EQ(0, Data.Kept);150 EXPECT_EQ(1, Data.Discarded);151 152#if GTEST_HAS_DEATH_TEST153 EXPECT_DEATH(consumeError(F.keep()),154 "some/file/path: output already closed");155 EXPECT_DEATH(consumeError(F.discard()),156 "some/file/path: output already closed");157#endif158 }159 EXPECT_EQ(0, Data.Kept);160 EXPECT_EQ(1, Data.Discarded);161}162 163TEST(VirtualOutputFileTest, discardError) {164 StringRef Content = "some data";165 MockOutputFileData Data;166 Data.Discarder = createCustomError;167 {168 OutputFile F("some/file/path", createMockOutput(Data));169 F.discardOnDestroy(Data.getHandler());170 F << Content;171 EXPECT_EQ(Content, Data.V);172 EXPECT_THAT_ERROR(F.discard(), FailedWithMessage("custom error"));173 EXPECT_FALSE(F.isOpen());174 EXPECT_EQ(0, Data.Kept);175 EXPECT_EQ(1, Data.Discarded);176 EXPECT_EQ(0, Data.Handled);177 }178 EXPECT_EQ(0, Data.Kept);179 EXPECT_EQ(1, Data.Discarded);180 EXPECT_EQ(0, Data.Handled);181}182 183TEST(VirtualOutputFileTest, discardProxy) {184 StringRef Content = "some data";185 MockOutputFileData Data;186 OutputFile F("some/file/path", createMockOutput(Data));187 F.discardOnDestroy(Data.getHandler());188 189 std::unique_ptr<raw_pwrite_stream> Proxy;190 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());191 *Proxy << Content;192 EXPECT_EQ(Content, Data.V);193 194 EXPECT_THAT_ERROR(F.discard(), Succeeded());195 EXPECT_FALSE(F.isOpen());196 EXPECT_EQ(0, Data.Kept);197 EXPECT_EQ(1, Data.Discarded);198}199 200TEST(VirtualOutputFileTest, discardProxyFlush) {201 StringRef Content = "some data";202 MockOutputFileData Data;203 OutputFile F("some/file/path", createMockOutput(Data));204 F.discardOnDestroy(Data.getHandler());205 F.getOS().SetBufferSize(Content.size() * 2);206 207 std::unique_ptr<raw_pwrite_stream> Proxy;208 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());209 *Proxy << Content;210 EXPECT_EQ("", Data.V);211 EXPECT_THAT_ERROR(F.discard(), Succeeded());212 EXPECT_EQ(Content, Data.V);213 EXPECT_FALSE(F.isOpen());214 EXPECT_EQ(0, Data.Kept);215 EXPECT_EQ(1, Data.Discarded);216}217 218TEST(VirtualOutputFileTest, keep) {219 StringRef Content = "some data";220 MockOutputFileData Data;221 {222 OutputFile F("some/file/path", createMockOutput(Data));223 F.discardOnDestroy(Data.getHandler());224 F << Content;225 EXPECT_EQ(Content, Data.V);226 227 EXPECT_THAT_ERROR(F.keep(), Succeeded());228 EXPECT_FALSE(F.isOpen());229 EXPECT_EQ(1, Data.Kept);230 EXPECT_EQ(0, Data.Discarded);231 232#if GTEST_HAS_DEATH_TEST233 EXPECT_DEATH(consumeError(F.keep()),234 "some/file/path: output already closed");235 EXPECT_DEATH(consumeError(F.discard()),236 "some/file/path: output already closed");237#endif238 }239 EXPECT_EQ(1, Data.Kept);240 EXPECT_EQ(0, Data.Discarded);241}242 243TEST(VirtualOutputFileTest, keepError) {244 StringRef Content = "some data";245 MockOutputFileData Data;246 Data.Keeper = createCustomError;247 {248 OutputFile F("some/file/path", createMockOutput(Data));249 F.discardOnDestroy(Data.getHandler());250 F << Content;251 EXPECT_EQ(Content, Data.V);252 253 EXPECT_THAT_ERROR(F.keep(), FailedWithMessage("custom error"));254 EXPECT_FALSE(F.isOpen());255 EXPECT_EQ(1, Data.Kept);256 EXPECT_EQ(0, Data.Discarded);257 EXPECT_EQ(0, Data.Handled);258 }259 EXPECT_EQ(1, Data.Kept);260 EXPECT_EQ(0, Data.Discarded);261 EXPECT_EQ(0, Data.Handled);262}263 264TEST(VirtualOutputFileTest, keepProxy) {265 StringRef Content = "some data";266 MockOutputFileData Data;267 OutputFile F("some/file/path", createMockOutput(Data));268 F.discardOnDestroy(Data.getHandler());269 270 std::unique_ptr<raw_pwrite_stream> Proxy;271 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());272 *Proxy << Content;273 EXPECT_EQ(Content, Data.V);274 Proxy.reset();275 EXPECT_THAT_ERROR(F.keep(), Succeeded());276 EXPECT_FALSE(F.isOpen());277 EXPECT_EQ(1, Data.Kept);278 EXPECT_EQ(0, Data.Discarded);279}280 281#if GTEST_HAS_DEATH_TEST282TEST(VirtualOutputFileTest, keepProxyStillOpen) {283 StringRef Content = "some data";284 MockOutputFileData Data;285 OutputFile F("some/file/path", createMockOutput(Data));286 F.discardOnDestroy(Data.getHandler());287 288 std::unique_ptr<raw_pwrite_stream> Proxy;289 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());290 *Proxy << Content;291 EXPECT_EQ(Content, Data.V);292 EXPECT_DEATH(consumeError(F.keep()), "some/file/path: output has open proxy");293}294#endif295 296TEST(VirtualOutputFileTest, keepProxyFlush) {297 StringRef Content = "some data";298 MockOutputFileData Data;299 OutputFile F("some/file/path", createMockOutput(Data));300 F.discardOnDestroy(Data.getHandler());301 F.getOS().SetBufferSize(Content.size() * 2);302 303 std::unique_ptr<raw_pwrite_stream> Proxy;304 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());305 *Proxy << Content;306 EXPECT_EQ("", Data.V);307 Proxy.reset();308 EXPECT_THAT_ERROR(F.keep(), Succeeded());309 EXPECT_EQ(Content, Data.V);310 EXPECT_FALSE(F.isOpen());311 EXPECT_EQ(1, Data.Kept);312 EXPECT_EQ(0, Data.Discarded);313}314 315TEST(VirtualOutputFileTest, TwoProxies) {316 StringRef Content = "some data";317 MockOutputFileData Data;318 319 OutputFile F("some/file/path", createMockOutput(Data));320 F.discardOnDestroy(Data.getHandler());321 322 // Can't have two open proxies at once.323 {324 std::unique_ptr<raw_pwrite_stream> Proxy;325 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());326 EXPECT_THAT_ERROR(327 F.createProxy().takeError(),328 FailedWithMessage("some/file/path: output has open proxy"));329 }330 EXPECT_EQ(0, Data.Kept);331 EXPECT_EQ(0, Data.Discarded);332 333 // A second proxy after the first closes should work...334 {335 std::unique_ptr<raw_pwrite_stream> Proxy;336 EXPECT_THAT_ERROR(F.createProxy().moveInto(Proxy), Succeeded());337 *Proxy << Content;338 EXPECT_EQ(Content, Data.V);339 }340}341 342} // end namespace343