blob: dae377b49d562854275df100ac2b0158ef051928 [file] [log] [blame]
// 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);
}