diff --git a/lldb/include/lldb/Utility/Reproducer.h b/lldb/include/lldb/Utility/Reproducer.h --- a/lldb/include/lldb/Utility/Reproducer.h +++ b/lldb/include/lldb/Utility/Reproducer.h @@ -159,6 +159,9 @@ static char ID; }; +/// The recorder is a small object handed out by a provider to record data. It +/// is commonly used in combination with a MultiProvider which is meant to +/// record information for multiple instances of the same source of data. class AbstractRecorder { protected: AbstractRecorder(const FileSpec &filename, std::error_code &ec) @@ -181,6 +184,7 @@ bool m_record; }; +/// Recorder that records its data as text to a file. class DataRecorder : public AbstractRecorder { public: DataRecorder(const FileSpec &filename, std::error_code &ec) @@ -199,24 +203,88 @@ } }; -class CommandProvider : public Provider { +/// Recorder that records its data as YAML to a file. +class YamlRecorder : public AbstractRecorder { +public: + YamlRecorder(const FileSpec &filename, std::error_code &ec) + : AbstractRecorder(filename, ec) {} + + static llvm::Expected> + Create(const FileSpec &filename); + + template void Record(const T &t) { + if (!m_record) + return; + llvm::yaml::Output yout(m_os); + // The YAML traits are defined as non-const because they are used for + // serialization and deserialization. The cast is safe because + // serialization doesn't modify the object. + yout << const_cast(t); + m_os.flush(); + } +}; + +/// The MultiProvider is a provider that hands out recorder which can be used +/// to capture data for different instances of the same object. The recorders +/// can be passed around or stored as an instance member. +/// +/// The Info::file for the MultiProvider contains an index of files for every +/// recorder. Use the MultiLoader to read the index and get the individual +/// files. +template +class MultiProvider : public repro::Provider { +public: + MultiProvider(const FileSpec &directory) : Provider(directory) {} + + T *GetNewRecorder() { + std::size_t i = m_recorders.size() + 1; + std::string filename = (llvm::Twine(V::Info::name) + llvm::Twine("-") + + llvm::Twine(i) + llvm::Twine(".yaml")) + .str(); + auto recorder_or_error = + T::Create(this->GetRoot().CopyByAppendingPathComponent(filename)); + if (!recorder_or_error) { + llvm::consumeError(recorder_or_error.takeError()); + return nullptr; + } + + m_recorders.push_back(std::move(*recorder_or_error)); + return m_recorders.back().get(); + } + + void Keep() override { + std::vector files; + for (auto &recorder : m_recorders) { + recorder->Stop(); + files.push_back(recorder->GetFilename().GetPath()); + } + + FileSpec file = this->GetRoot().CopyByAppendingPathComponent(V::Info::file); + std::error_code ec; + llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text); + if (ec) + return; + llvm::yaml::Output yout(os); + yout << files; + } + + void Discard() override { m_recorders.clear(); } + +private: + std::vector> m_recorders; +}; + +class CommandProvider : public MultiProvider { public: struct Info { static const char *name; static const char *file; }; - CommandProvider(const FileSpec &directory) : Provider(directory) {} - - DataRecorder *GetNewDataRecorder(); - - void Keep() override; - void Discard() override; + CommandProvider(const FileSpec &directory) + : MultiProvider(directory) {} static char ID; - -private: - std::vector> m_data_recorders; }; /// The generator is responsible for the logic needed to generate a @@ -360,6 +428,8 @@ mutable std::mutex m_mutex; }; +/// Loader for data captured with the MultiProvider. It will read the index and +/// return the path to the files in the index. template class MultiLoader { public: MultiLoader(std::vector files) : m_files(files) {} diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -313,7 +313,7 @@ repro::DataRecorder *recorder = nullptr; if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) - recorder = g->GetOrCreate().GetNewDataRecorder(); + recorder = g->GetOrCreate().GetNewRecorder(); FileSP file_sp = file.m_opaque_sp; diff --git a/lldb/source/Utility/Reproducer.cpp b/lldb/source/Utility/Reproducer.cpp --- a/lldb/source/Utility/Reproducer.cpp +++ b/lldb/source/Utility/Reproducer.cpp @@ -272,40 +272,15 @@ return std::move(recorder); } -DataRecorder *CommandProvider::GetNewDataRecorder() { - std::size_t i = m_data_recorders.size() + 1; - std::string filename = (llvm::Twine(Info::name) + llvm::Twine("-") + - llvm::Twine(i) + llvm::Twine(".yaml")) - .str(); - auto recorder_or_error = - DataRecorder::Create(GetRoot().CopyByAppendingPathComponent(filename)); - if (!recorder_or_error) { - llvm::consumeError(recorder_or_error.takeError()); - return nullptr; - } - - m_data_recorders.push_back(std::move(*recorder_or_error)); - return m_data_recorders.back().get(); -} - -void CommandProvider::Keep() { - std::vector files; - for (auto &recorder : m_data_recorders) { - recorder->Stop(); - files.push_back(recorder->GetFilename().GetPath()); - } - - FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file); +llvm::Expected> +YamlRecorder::Create(const FileSpec &filename) { std::error_code ec; - llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text); + auto recorder = std::make_unique(std::move(filename), ec); if (ec) - return; - yaml::Output yout(os); - yout << files; + return llvm::errorCodeToError(ec); + return std::move(recorder); } -void CommandProvider::Discard() { m_data_recorders.clear(); } - void VersionProvider::Keep() { FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file); std::error_code ec; diff --git a/lldb/unittests/Utility/ReproducerTest.cpp b/lldb/unittests/Utility/ReproducerTest.cpp --- a/lldb/unittests/Utility/ReproducerTest.cpp +++ b/lldb/unittests/Utility/ReproducerTest.cpp @@ -9,6 +9,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Error.h" @@ -31,8 +32,25 @@ static char ID; }; +class YamlMultiProvider + : public MultiProvider { +public: + struct Info { + static const char *name; + static const char *file; + }; + + YamlMultiProvider(const FileSpec &directory) : MultiProvider(directory) {} + + static char ID; +}; + const char *DummyProvider::Info::name = "dummy"; const char *DummyProvider::Info::file = "dummy.yaml"; +const char *YamlMultiProvider::Info::name = "mutli"; +const char *YamlMultiProvider::Info::file = "mutli.yaml"; +char DummyProvider::ID = 0; +char YamlMultiProvider::ID = 0; class DummyReproducer : public Reproducer { public: @@ -42,7 +60,25 @@ using Reproducer::SetReplay; }; -char DummyProvider::ID = 0; +struct YamlData { + YamlData() : i(-1) {} + YamlData(int i) : i(i) {} + int i; +}; + +inline bool operator==(const YamlData &LHS, const YamlData &RHS) { + return LHS.i == RHS.i; +} + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(YamlData) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &io, YamlData &Y) { io.mapRequired("i", Y.i); }; +}; +} // namespace yaml +} // namespace llvm TEST(ReproducerTest, SetCapture) { DummyReproducer reproducer; @@ -144,3 +180,83 @@ auto &provider_alt = generator.GetOrCreate(); EXPECT_EQ(&provider, &provider_alt); } + +TEST(GeneratorTest, YamlMultiProvider) { + SmallString<128> root; + std::error_code ec = llvm::sys::fs::createUniqueDirectory("reproducer", root); + ASSERT_FALSE(static_cast(ec)); + + auto cleanup = llvm::make_scope_exit( + [&] { EXPECT_FALSE(llvm::sys::fs::remove_directories(root.str())); }); + + YamlData data0(0); + YamlData data1(1); + YamlData data2(2); + YamlData data3(3); + + { + DummyReproducer reproducer; + EXPECT_THAT_ERROR(reproducer.SetCapture(FileSpec(root.str())), Succeeded()); + + auto &generator = *reproducer.GetGenerator(); + auto *provider = generator.Create(); + ASSERT_NE(nullptr, provider); + + auto *recorder = provider->GetNewRecorder(); + ASSERT_NE(nullptr, recorder); + recorder->Record(data0); + recorder->Record(data1); + + recorder = provider->GetNewRecorder(); + ASSERT_NE(nullptr, recorder); + recorder->Record(data2); + recorder->Record(data3); + + generator.Keep(); + } + + { + DummyReproducer reproducer; + EXPECT_THAT_ERROR(reproducer.SetReplay(FileSpec(root.str())), Succeeded()); + + auto &loader = *reproducer.GetLoader(); + std::unique_ptr> multi_loader = + repro::MultiLoader::Create(&loader); + + // Read the first file. + { + llvm::Optional file = multi_loader->GetNextFile(); + EXPECT_TRUE(static_cast(file)); + + auto buffer = llvm::MemoryBuffer::getFile(*file); + EXPECT_TRUE(static_cast(buffer)); + + yaml::Input yin((*buffer)->getBuffer()); + std::vector data; + yin >> data; + + ASSERT_EQ(data.size(), 2U); + EXPECT_THAT(data, testing::ElementsAre(data0, data1)); + } + + // Read the second file. + { + llvm::Optional file = multi_loader->GetNextFile(); + EXPECT_TRUE(static_cast(file)); + + auto buffer = llvm::MemoryBuffer::getFile(*file); + EXPECT_TRUE(static_cast(buffer)); + + yaml::Input yin((*buffer)->getBuffer()); + std::vector data; + yin >> data; + + ASSERT_EQ(data.size(), 2U); + EXPECT_THAT(data, testing::ElementsAre(data2, data3)); + } + + // There is no third file. + llvm::Optional file = multi_loader->GetNextFile(); + EXPECT_FALSE(static_cast(file)); + } +}