aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid 'Digit' Turner <digit+github@google.com>2024-05-19 13:30:37 +0200
committerDavid 'Digit' Turner <digit+github@google.com>2024-05-19 17:04:12 +0200
commit986d8440cac8bcc673e648db75278ab677b5c4e3 (patch)
tree92edc5abe67fab96d2e56adc0cb0f896428e4252
parent805cf31757056b7fb58c087afcb8714770de83cc (diff)
downloadninja-986d8440cac8bcc673e648db75278ab677b5c4e3.tar.gz
Add Explanations class
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.
-rw-r--r--CMakeLists.txt1
-rwxr-xr-xconfigure.py1
-rw-r--r--src/explanations.h89
-rw-r--r--src/explanations_test.cc97
4 files changed, 188 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 78243b7..5af5114 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/configure.py b/configure.py
index 2b16618..c88daad 100755
--- a/configure.py
+++ b/configure.py
@@ -638,6 +638,7 @@ if gtest_src_dir:
'disk_interface_test',
'dyndep_parser_test',
'edit_distance_test',
+ 'explanations_test',
'graph_test',
'json_test',
'lexer_test',
diff --git a/src/explanations.h b/src/explanations.h
new file mode 100644
index 0000000..babebd4
--- /dev/null
+++ b/src/explanations.h
@@ -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_;
+};
diff --git a/src/explanations_test.cc b/src/explanations_test.cc
new file mode 100644
index 0000000..e46600f
--- /dev/null
+++ b/src/explanations_test.cc
@@ -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());
+}