Skip to content

Commit

Permalink
Add Explanations class
Browse files Browse the repository at this point in the history
This class will be used to replace the global
mutable variable |explanations_| currently defined
in debug_flags.cc, and updated from random locations
of the source code through the `record_explanation()`
function.
  • Loading branch information
digit-google committed May 19, 2024
1 parent 805cf31 commit 986d844
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ if(BUILD_TESTING)
src/disk_interface_test.cc
src/dyndep_parser_test.cc
src/edit_distance_test.cc
src/explanations_test.cc
src/graph_test.cc
src/json_test.cc
src/lexer_test.cc
Expand Down
1 change: 1 addition & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ def has_re2c() -> bool:
'disk_interface_test',
'dyndep_parser_test',
'edit_distance_test',
'explanations_test',
'graph_test',
'json_test',
'lexer_test',
Expand Down
89 changes: 89 additions & 0 deletions src/explanations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <stdarg.h>
#include <stdio.h>

#include <string>
#include <unordered_map>
#include <vector>

/// A class used to record a list of explanation strings associated
/// with a given 'item' pointer. This is used to implement the
/// `-d explain` feature.
struct Explanations {
public:
/// Record an explanation for |item| if this instance is enabled.
void Record(const void* item, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
RecordArgs(item, fmt, args);
va_end(args);
}

/// Same as Record(), but uses a va_list to pass formatting arguments.
void RecordArgs(const void* item, const char* fmt, va_list args) {
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), fmt, args);
map_[item].emplace_back(buffer);
}

/// Lookup the explanations recorded for |item|, and append them
/// to |*out|, if any.
void LookupAndAppend(const void* item, std::vector<std::string>* out) {
auto it = map_.find(item);
if (it == map_.end())
return;

for (const auto& explanation : it->second)
out->push_back(explanation);
}

private:
bool enabled_ = false;
std::unordered_map<const void*, std::vector<std::string>> map_;
};

/// Convenience wrapper for an Explanations pointer, which can be null
/// if no explanations need to be recorded.
struct OptionalExplanations {
OptionalExplanations(Explanations* explanations)
: explanations_(explanations) {}

void Record(const void* item, const char* fmt, ...) {
if (explanations_) {
va_list args;
va_start(args, fmt);
explanations_->RecordArgs(item, fmt, args);
va_end(args);
}
}

void RecordArgs(const void* item, const char* fmt, va_list args) {
if (explanations_)
explanations_->RecordArgs(item, fmt, args);
}

void LookupAndAppend(const void* item, std::vector<std::string>* out) {
if (explanations_)
explanations_->LookupAndAppend(item, out);
}

Explanations* ptr() const { return explanations_; }

private:
Explanations* explanations_;
};
97 changes: 97 additions & 0 deletions src/explanations_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "explanations.h"

#include "test.h"

namespace {

const void* MakeItem(size_t v) {
return reinterpret_cast<const void*>(v);
}

} // namespace

TEST(Explanations, Explanations) {
Explanations exp;

exp.Record(MakeItem(1), "first explanation");
exp.Record(MakeItem(1), "second explanation");
exp.Record(MakeItem(2), "third explanation");
exp.Record(MakeItem(2), "fourth %s", "explanation");

std::vector<std::string> list;

exp.LookupAndAppend(MakeItem(0), &list);
ASSERT_TRUE(list.empty());

exp.LookupAndAppend(MakeItem(1), &list);
ASSERT_EQ(2u, list.size());
EXPECT_EQ(list[0], "first explanation");
EXPECT_EQ(list[1], "second explanation");

exp.LookupAndAppend(MakeItem(2), &list);
ASSERT_EQ(4u, list.size());
EXPECT_EQ(list[0], "first explanation");
EXPECT_EQ(list[1], "second explanation");
EXPECT_EQ(list[2], "third explanation");
EXPECT_EQ(list[3], "fourth explanation");
}

TEST(Explanations, OptionalExplanationsNonNull) {
Explanations parent;
OptionalExplanations exp(&parent);

exp.Record(MakeItem(1), "first explanation");
exp.Record(MakeItem(1), "second explanation");
exp.Record(MakeItem(2), "third explanation");
exp.Record(MakeItem(2), "fourth %s", "explanation");

std::vector<std::string> list;

exp.LookupAndAppend(MakeItem(0), &list);
ASSERT_TRUE(list.empty());

exp.LookupAndAppend(MakeItem(1), &list);
ASSERT_EQ(2u, list.size());
EXPECT_EQ(list[0], "first explanation");
EXPECT_EQ(list[1], "second explanation");

exp.LookupAndAppend(MakeItem(2), &list);
ASSERT_EQ(4u, list.size());
EXPECT_EQ(list[0], "first explanation");
EXPECT_EQ(list[1], "second explanation");
EXPECT_EQ(list[2], "third explanation");
EXPECT_EQ(list[3], "fourth explanation");
}

TEST(Explanations, OptionalExplanationsWithNullPointer) {
OptionalExplanations exp(nullptr);

exp.Record(MakeItem(1), "first explanation");
exp.Record(MakeItem(1), "second explanation");
exp.Record(MakeItem(2), "third explanation");
exp.Record(MakeItem(2), "fourth %s", "explanation");

std::vector<std::string> list;
exp.LookupAndAppend(MakeItem(0), &list);
ASSERT_TRUE(list.empty());

exp.LookupAndAppend(MakeItem(1), &list);
ASSERT_TRUE(list.empty());

exp.LookupAndAppend(MakeItem(2), &list);
ASSERT_TRUE(list.empty());
}

0 comments on commit 986d844

Please sign in to comment.