diff options
author | David 'Digit' Turner <digit+github@google.com> | 2024-05-19 13:30:37 +0200 |
---|---|---|
committer | David 'Digit' Turner <digit+github@google.com> | 2024-05-19 17:04:12 +0200 |
commit | 986d8440cac8bcc673e648db75278ab677b5c4e3 (patch) | |
tree | 92edc5abe67fab96d2e56adc0cb0f896428e4252 | |
parent | 805cf31757056b7fb58c087afcb8714770de83cc (diff) | |
download | ninja-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.txt | 1 | ||||
-rwxr-xr-x | configure.py | 1 | ||||
-rw-r--r-- | src/explanations.h | 89 | ||||
-rw-r--r-- | src/explanations_test.cc | 97 |
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()); +} |