| // Copyright 2019 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 <memory> |
| |
| #include "deps_log.h" |
| #include "graph.h" |
| #include "missing_deps.h" |
| #include "state.h" |
| #include "test.h" |
| |
| const char kTestDepsLogFilename[] = "MissingDepTest-tempdepslog"; |
| |
| class MissingDependencyTestDelegate : public MissingDependencyScannerDelegate { |
| void OnMissingDep(Node* node, const std::string& path, |
| const Rule& generator) {} |
| }; |
| |
| struct MissingDependencyScannerTest : public testing::Test { |
| MissingDependencyScannerTest() |
| : generator_rule_("generator_rule"), compile_rule_("compile_rule"), |
| scanner_(&delegate_, &deps_log_, &state_, &filesystem_) { |
| std::string err; |
| deps_log_.OpenForWrite(kTestDepsLogFilename, &err); |
| EXPECT_EQ("", err); |
| } |
| |
| ~MissingDependencyScannerTest() { |
| // Remove test file. |
| deps_log_.Close(); |
| } |
| |
| MissingDependencyScanner& scanner() { return scanner_; } |
| |
| void RecordDepsLogDep(const std::string& from, const std::string& to) { |
| Node* node_deps[] = { state_.LookupNode(to) }; |
| deps_log_.RecordDeps(state_.LookupNode(from), 0, 1, node_deps); |
| } |
| |
| void ProcessAllNodes() { |
| std::string err; |
| std::vector<Node*> nodes = state_.RootNodes(&err); |
| EXPECT_EQ("", err); |
| for (std::vector<Node*>::iterator it = nodes.begin(); it != nodes.end(); |
| ++it) { |
| scanner().ProcessNode(*it); |
| } |
| } |
| |
| void CreateInitialState() { |
| EvalString deps_type; |
| deps_type.AddText("gcc"); |
| compile_rule_.AddBinding("deps", deps_type); |
| generator_rule_.AddBinding("deps", deps_type); |
| Edge* header_edge = state_.AddEdge(&generator_rule_); |
| state_.AddOut(header_edge, "generated_header", 0, nullptr); |
| Edge* compile_edge = state_.AddEdge(&compile_rule_); |
| state_.AddOut(compile_edge, "compiled_object", 0, nullptr); |
| } |
| |
| void CreateGraphDependencyBetween(const char* from, const char* to) { |
| Node* from_node = state_.LookupNode(from); |
| Edge* from_edge = from_node->in_edge(); |
| state_.AddIn(from_edge, to, 0); |
| } |
| |
| void AssertMissingDependencyBetween(const char* flaky, const char* generated, |
| Rule* rule) { |
| Node* flaky_node = state_.LookupNode(flaky); |
| ASSERT_EQ(1u, scanner().nodes_missing_deps_.count(flaky_node)); |
| Node* generated_node = state_.LookupNode(generated); |
| ASSERT_EQ(1u, scanner().generated_nodes_.count(generated_node)); |
| ASSERT_EQ(1u, scanner().generator_rules_.count(rule)); |
| } |
| |
| ScopedFilePath scoped_file_path_ = kTestDepsLogFilename; |
| MissingDependencyTestDelegate delegate_; |
| Rule generator_rule_; |
| Rule compile_rule_; |
| DepsLog deps_log_; |
| State state_; |
| VirtualFileSystem filesystem_; |
| MissingDependencyScanner scanner_; |
| }; |
| |
| TEST_F(MissingDependencyScannerTest, EmptyGraph) { |
| ProcessAllNodes(); |
| ASSERT_FALSE(scanner().HadMissingDeps()); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, NoMissingDep) { |
| CreateInitialState(); |
| ProcessAllNodes(); |
| ASSERT_FALSE(scanner().HadMissingDeps()); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, MissingDepPresent) { |
| CreateInitialState(); |
| // compiled_object uses generated_header, without a proper dependency |
| RecordDepsLogDep("compiled_object", "generated_header"); |
| ProcessAllNodes(); |
| ASSERT_TRUE(scanner().HadMissingDeps()); |
| ASSERT_EQ(1u, scanner().nodes_missing_deps_.size()); |
| ASSERT_EQ(1u, scanner().missing_dep_path_count_); |
| AssertMissingDependencyBetween("compiled_object", "generated_header", |
| &generator_rule_); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, MissingDepFixedDirect) { |
| CreateInitialState(); |
| // Adding the direct dependency fixes the missing dep |
| CreateGraphDependencyBetween("compiled_object", "generated_header"); |
| RecordDepsLogDep("compiled_object", "generated_header"); |
| ProcessAllNodes(); |
| ASSERT_FALSE(scanner().HadMissingDeps()); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, MissingDepFixedIndirect) { |
| CreateInitialState(); |
| // Adding an indirect dependency also fixes the issue |
| Edge* intermediate_edge = state_.AddEdge(&generator_rule_); |
| state_.AddOut(intermediate_edge, "intermediate", 0, nullptr); |
| CreateGraphDependencyBetween("compiled_object", "intermediate"); |
| CreateGraphDependencyBetween("intermediate", "generated_header"); |
| RecordDepsLogDep("compiled_object", "generated_header"); |
| ProcessAllNodes(); |
| ASSERT_FALSE(scanner().HadMissingDeps()); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, CyclicMissingDep) { |
| CreateInitialState(); |
| RecordDepsLogDep("generated_header", "compiled_object"); |
| RecordDepsLogDep("compiled_object", "generated_header"); |
| // In case of a cycle, both paths are reported (and there is |
| // no way to fix the issue by adding deps). |
| ProcessAllNodes(); |
| ASSERT_TRUE(scanner().HadMissingDeps()); |
| ASSERT_EQ(2u, scanner().nodes_missing_deps_.size()); |
| ASSERT_EQ(2u, scanner().missing_dep_path_count_); |
| AssertMissingDependencyBetween("compiled_object", "generated_header", |
| &generator_rule_); |
| AssertMissingDependencyBetween("generated_header", "compiled_object", |
| &compile_rule_); |
| } |
| |
| TEST_F(MissingDependencyScannerTest, CycleInGraph) { |
| CreateInitialState(); |
| CreateGraphDependencyBetween("compiled_object", "generated_header"); |
| CreateGraphDependencyBetween("generated_header", "compiled_object"); |
| // The missing-deps tool doesn't deal with cycles in the graph, because |
| // there will be an error loading the graph before we get to the tool. |
| // This test is to illustrate that. |
| std::string err; |
| std::vector<Node*> nodes = state_.RootNodes(&err); |
| ASSERT_NE("", err); |
| } |