[Lldb-commits] [lldb] 169c832 - [ldb/Reproducers] Add YamlRecorder and MultiProvider
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Fri Jul 10 12:48:30 PDT 2020
Author: Jonas Devlieghere
Date: 2020-07-10T12:48:22-07:00
New Revision: 169c83208f37f8e5539b1386030d9f3e4b15f16a
URL: https://github.com/llvm/llvm-project/commit/169c83208f37f8e5539b1386030d9f3e4b15f16a
DIFF: https://github.com/llvm/llvm-project/commit/169c83208f37f8e5539b1386030d9f3e4b15f16a.diff
LOG: [ldb/Reproducers] Add YamlRecorder and MultiProvider
This patch does several things that are all closely related:
- It introduces a new YamlRecorder as a counterpart to the existing
DataRecorder. As the name suggests the former serializes data as yaml
while the latter uses raw texts or bytes.
- It introduces a new MultiProvider base class which can be backed by
either a DataRecorder or a YamlRecorder.
- It reimplements the CommandProvider in terms of the new
MultiProvider.
Finally, it adds unit testing coverage for the MultiProvider, a naive
YamlProvider built on top of the new YamlRecorder and the existing
MutliLoader.
Differential revision: https://reviews.llvm.org/D83441
Added:
Modified:
lldb/include/lldb/Utility/Reproducer.h
lldb/source/API/SBDebugger.cpp
lldb/source/Utility/Reproducer.cpp
lldb/unittests/Utility/ReproducerTest.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Utility/Reproducer.h b/lldb/include/lldb/Utility/Reproducer.h
index 5d810460a0c5..ab673e5e0019 100644
--- a/lldb/include/lldb/Utility/Reproducer.h
+++ b/lldb/include/lldb/Utility/Reproducer.h
@@ -159,6 +159,9 @@ class WorkingDirectoryProvider : public Provider<WorkingDirectoryProvider> {
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 @@ class AbstractRecorder {
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 DataRecorder : public AbstractRecorder {
}
};
-class CommandProvider : public Provider<CommandProvider> {
+/// 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<std::unique_ptr<YamlRecorder>>
+ Create(const FileSpec &filename);
+
+ template <typename T> 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 &>(t);
+ m_os.flush();
+ }
+};
+
+/// The MultiProvider is a provider that hands out recorder which can be used
+/// to capture data for
diff erent 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 <typename T, typename V>
+class MultiProvider : public repro::Provider<V> {
+public:
+ MultiProvider(const FileSpec &directory) : Provider<V>(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<std::string> 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<std::unique_ptr<T>> m_recorders;
+};
+
+class CommandProvider : public MultiProvider<DataRecorder, CommandProvider> {
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<DataRecorder, CommandProvider>(directory) {}
static char ID;
-
-private:
- std::vector<std::unique_ptr<DataRecorder>> m_data_recorders;
};
/// The generator is responsible for the logic needed to generate a
@@ -360,6 +428,8 @@ class Reproducer {
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 <typename T> class MultiLoader {
public:
MultiLoader(std::vector<std::string> files) : m_files(files) {}
diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index 978d0befa5ff..5f62987f37da 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -313,7 +313,7 @@ SBError SBDebugger::SetInputFile(SBFile file) {
repro::DataRecorder *recorder = nullptr;
if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator())
- recorder = g->GetOrCreate<repro::CommandProvider>().GetNewDataRecorder();
+ recorder = g->GetOrCreate<repro::CommandProvider>().GetNewRecorder();
FileSP file_sp = file.m_opaque_sp;
diff --git a/lldb/source/Utility/Reproducer.cpp b/lldb/source/Utility/Reproducer.cpp
index eb7d96baab57..7620ab2c389d 100644
--- a/lldb/source/Utility/Reproducer.cpp
+++ b/lldb/source/Utility/Reproducer.cpp
@@ -272,40 +272,15 @@ DataRecorder::Create(const FileSpec &filename) {
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<std::string> files;
- for (auto &recorder : m_data_recorders) {
- recorder->Stop();
- files.push_back(recorder->GetFilename().GetPath());
- }
-
- FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file);
+llvm::Expected<std::unique_ptr<YamlRecorder>>
+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<YamlRecorder>(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
index 1ada0b32827c..5a9dea3450f0 100644
--- 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 @@ class DummyProvider : public repro::Provider<DummyProvider> {
static char ID;
};
+class YamlMultiProvider
+ : public MultiProvider<YamlRecorder, YamlMultiProvider> {
+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 @@ class DummyReproducer : public Reproducer {
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<YamlData> {
+ 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 @@ TEST(GeneratorTest, GetOrCreate) {
auto &provider_alt = generator.GetOrCreate<DummyProvider>();
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<bool>(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<YamlMultiProvider>();
+ 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<repro::MultiLoader<YamlMultiProvider>> multi_loader =
+ repro::MultiLoader<YamlMultiProvider>::Create(&loader);
+
+ // Read the first file.
+ {
+ llvm::Optional<std::string> file = multi_loader->GetNextFile();
+ EXPECT_TRUE(static_cast<bool>(file));
+
+ auto buffer = llvm::MemoryBuffer::getFile(*file);
+ EXPECT_TRUE(static_cast<bool>(buffer));
+
+ yaml::Input yin((*buffer)->getBuffer());
+ std::vector<YamlData> data;
+ yin >> data;
+
+ ASSERT_EQ(data.size(), 2U);
+ EXPECT_THAT(data, testing::ElementsAre(data0, data1));
+ }
+
+ // Read the second file.
+ {
+ llvm::Optional<std::string> file = multi_loader->GetNextFile();
+ EXPECT_TRUE(static_cast<bool>(file));
+
+ auto buffer = llvm::MemoryBuffer::getFile(*file);
+ EXPECT_TRUE(static_cast<bool>(buffer));
+
+ yaml::Input yin((*buffer)->getBuffer());
+ std::vector<YamlData> data;
+ yin >> data;
+
+ ASSERT_EQ(data.size(), 2U);
+ EXPECT_THAT(data, testing::ElementsAre(data2, data3));
+ }
+
+ // There is no third file.
+ llvm::Optional<std::string> file = multi_loader->GetNextFile();
+ EXPECT_FALSE(static_cast<bool>(file));
+ }
+}
More information about the lldb-commits
mailing list