Skip to content

Commit

Permalink
[ldb/Reproducers] Add YamlRecorder and MultiProvider
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JDevlieghere committed Jul 10, 2020
1 parent ea201e8 commit 169c832
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 42 deletions.
90 changes: 80 additions & 10 deletions lldb/include/lldb/Utility/Reproducer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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 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 <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
Expand Down Expand Up @@ -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) {}
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/API/SBDebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
35 changes: 5 additions & 30 deletions lldb/source/Utility/Reproducer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
118 changes: 117 additions & 1 deletion lldb/unittests/Utility/ReproducerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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:
Expand All @@ -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;
Expand Down Expand Up @@ -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));
}
}

0 comments on commit 169c832

Please sign in to comment.