blob: f54c666a3f820439d65e40ecb30f2746e8ca4d6c [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/gn/scope.h"
#include "tools/gn/input_file.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/source_file.h"
#include "tools/gn/template.h"
#include "tools/gn/test_with_scope.h"
#include "util/test/test.h"
namespace {
bool HasStringValueEqualTo(const Scope* scope,
const char* name,
const char* expected_value) {
const Value* value = scope->GetValue(name);
if (!value)
return false;
if (value->type() != Value::STRING)
return false;
return value->string_value() == expected_value;
}
bool ContainsBuildDependencyFile(const Scope* scope,
const SourceFile& source_file) {
const auto& build_dependency_files = scope->build_dependency_files();
return build_dependency_files.end() !=
build_dependency_files.find(source_file);
}
} // namespace
TEST(Scope, InheritBuildDependencyFilesFromParent) {
TestWithScope setup;
SourceFile source_file = SourceFile("//a/BUILD.gn");
setup.scope()->AddBuildDependencyFile(source_file);
Scope new_scope(setup.scope());
EXPECT_EQ(1U, new_scope.build_dependency_files().size());
EXPECT_TRUE(ContainsBuildDependencyFile(&new_scope, source_file));
}
TEST(Scope, NonRecursiveMergeTo) {
TestWithScope setup;
// Make a pretend parse node with proper tracking that we can blame for the
// given value.
InputFile input_file(SourceFile("//foo"));
Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
"\"hello\"");
LiteralNode assignment;
assignment.set_value(assignment_token);
// Add some values to the scope.
Value old_value(&assignment, "hello");
setup.scope()->SetValue("v", old_value, &assignment);
std::string_view private_var_name("_private");
setup.scope()->SetValue(private_var_name, old_value, &assignment);
// Add some templates to the scope.
FunctionCallNode templ_definition;
scoped_refptr<Template> templ(new Template(setup.scope(), &templ_definition));
setup.scope()->AddTemplate("templ", templ.get());
scoped_refptr<Template> private_templ(
new Template(setup.scope(), &templ_definition));
setup.scope()->AddTemplate("_templ", private_templ.get());
// Detect collisions of values' values.
{
Scope new_scope(setup.settings());
Value new_value(&assignment, "goodbye");
new_scope.SetValue("v", new_value, &assignment);
Err err;
EXPECT_FALSE(setup.scope()->NonRecursiveMergeTo(
&new_scope, Scope::MergeOptions(), &assignment, "error", &err));
EXPECT_TRUE(err.has_error());
}
// Template name collisions.
{
Scope new_scope(setup.settings());
scoped_refptr<Template> new_templ(
new Template(&new_scope, &templ_definition));
new_scope.AddTemplate("templ", new_templ.get());
Err err;
EXPECT_FALSE(setup.scope()->NonRecursiveMergeTo(
&new_scope, Scope::MergeOptions(), &assignment, "error", &err));
EXPECT_TRUE(err.has_error());
}
// The clobber flag should just overwrite colliding values.
{
Scope new_scope(setup.settings());
Value new_value(&assignment, "goodbye");
new_scope.SetValue("v", new_value, &assignment);
Err err;
Scope::MergeOptions options;
options.clobber_existing = true;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
&assignment, "error", &err));
EXPECT_FALSE(err.has_error());
const Value* found_value = new_scope.GetValue("v");
ASSERT_TRUE(found_value);
EXPECT_TRUE(old_value == *found_value);
}
// Clobber flag for templates.
{
Scope new_scope(setup.settings());
scoped_refptr<Template> new_templ(
new Template(&new_scope, &templ_definition));
new_scope.AddTemplate("templ", new_templ.get());
Scope::MergeOptions options;
options.clobber_existing = true;
Err err;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
&assignment, "error", &err));
EXPECT_FALSE(err.has_error());
const Template* found_value = new_scope.GetTemplate("templ");
ASSERT_TRUE(found_value);
EXPECT_TRUE(templ.get() == found_value);
}
// Don't flag values that technically collide but have the same value.
{
Scope new_scope(setup.settings());
Value new_value(&assignment, "hello");
new_scope.SetValue("v", new_value, &assignment);
Err err;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
&new_scope, Scope::MergeOptions(), &assignment, "error", &err));
EXPECT_FALSE(err.has_error());
}
// Templates that technically collide but are the same.
{
Scope new_scope(setup.settings());
scoped_refptr<Template> new_templ(
new Template(&new_scope, &templ_definition));
new_scope.AddTemplate("templ", templ.get());
Err err;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
&new_scope, Scope::MergeOptions(), &assignment, "error", &err));
EXPECT_FALSE(err.has_error());
}
// Copy private values and templates.
{
Scope new_scope(setup.settings());
Err err;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(
&new_scope, Scope::MergeOptions(), &assignment, "error", &err));
EXPECT_FALSE(err.has_error());
EXPECT_TRUE(new_scope.GetValue(private_var_name));
EXPECT_TRUE(new_scope.GetTemplate("_templ"));
}
// Skip private values and templates.
{
Scope new_scope(setup.settings());
Err err;
Scope::MergeOptions options;
options.skip_private_vars = true;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
&assignment, "error", &err));
EXPECT_FALSE(err.has_error());
EXPECT_FALSE(new_scope.GetValue(private_var_name));
EXPECT_FALSE(new_scope.GetTemplate("_templ"));
}
// Don't mark used.
{
Scope new_scope(setup.settings());
Err err;
Scope::MergeOptions options;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
&assignment, "error", &err));
EXPECT_FALSE(err.has_error());
EXPECT_FALSE(new_scope.CheckForUnusedVars(&err));
EXPECT_TRUE(err.has_error());
}
// Mark dest used.
{
Scope new_scope(setup.settings());
Err err;
Scope::MergeOptions options;
options.mark_dest_used = true;
EXPECT_TRUE(setup.scope()->NonRecursiveMergeTo(&new_scope, options,
&assignment, "error", &err));
EXPECT_FALSE(err.has_error());
EXPECT_TRUE(new_scope.CheckForUnusedVars(&err));
EXPECT_FALSE(err.has_error());
}
// Build dependency files are merged.
{
Scope from_scope(setup.settings());
SourceFile source_file = SourceFile("//a/BUILD.gn");
from_scope.AddBuildDependencyFile(source_file);
Scope to_scope(setup.settings());
EXPECT_FALSE(ContainsBuildDependencyFile(&to_scope, source_file));
Scope::MergeOptions options;
Err err;
EXPECT_TRUE(from_scope.NonRecursiveMergeTo(&to_scope, options, &assignment,
"error", &err));
EXPECT_FALSE(err.has_error());
EXPECT_EQ(1U, to_scope.build_dependency_files().size());
EXPECT_TRUE(ContainsBuildDependencyFile(&to_scope, source_file));
}
}
TEST(Scope, MakeClosure) {
// Create 3 nested scopes [const root from setup] <- nested1 <- nested2.
TestWithScope setup;
// Make a pretend parse node with proper tracking that we can blame for the
// given value.
InputFile input_file(SourceFile("//foo"));
Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
"\"hello\"");
LiteralNode assignment;
assignment.set_value(assignment_token);
setup.scope()->SetValue("on_root", Value(&assignment, "on_root"),
&assignment);
// Root scope should be const from the nested caller's perspective.
Scope nested1(static_cast<const Scope*>(setup.scope()));
nested1.SetValue("on_one", Value(&assignment, "on_one"), &assignment);
Scope nested2(&nested1);
nested2.SetValue("on_one", Value(&assignment, "on_two"), &assignment);
nested2.SetValue("on_two", Value(&assignment, "on_two2"), &assignment);
// Making a closure from the root scope.
std::unique_ptr<Scope> result = setup.scope()->MakeClosure();
EXPECT_FALSE(result->containing()); // Should have no containing scope.
EXPECT_TRUE(result->GetValue("on_root")); // Value should be copied.
// Making a closure from the second nested scope.
result = nested2.MakeClosure();
EXPECT_EQ(setup.scope(),
result->containing()); // Containing scope should be the root.
EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_root", "on_root"));
EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_one", "on_two"));
EXPECT_TRUE(HasStringValueEqualTo(result.get(), "on_two", "on_two2"));
}
TEST(Scope, GetMutableValue) {
TestWithScope setup;
// Make a pretend parse node with proper tracking that we can blame for the
// given value.
InputFile input_file(SourceFile("//foo"));
Token assignment_token(Location(&input_file, 1, 1, 1), Token::STRING,
"\"hello\"");
LiteralNode assignment;
assignment.set_value(assignment_token);
const char kOnConst[] = "on_const";
const char kOnMutable1[] = "on_mutable1";
const char kOnMutable2[] = "on_mutable2";
Value value(&assignment, "hello");
// Create a root scope with one value.
Scope root_scope(setup.settings());
root_scope.SetValue(kOnConst, value, &assignment);
// Create a first nested scope with a different value.
const Scope* const_root_scope = &root_scope;
Scope mutable_scope1(const_root_scope);
mutable_scope1.SetValue(kOnMutable1, value, &assignment);
// Create a second nested scope with a different value.
Scope mutable_scope2(&mutable_scope1);
mutable_scope2.SetValue(kOnMutable2, value, &assignment);
// Check getting root scope values.
EXPECT_TRUE(mutable_scope2.GetValue(kOnConst, true));
EXPECT_FALSE(
mutable_scope2.GetMutableValue(kOnConst, Scope::SEARCH_NESTED, true));
// Test reading a value from scope 1.
Value* mutable1_result =
mutable_scope2.GetMutableValue(kOnMutable1, Scope::SEARCH_NESTED, false);
ASSERT_TRUE(mutable1_result);
EXPECT_TRUE(*mutable1_result == value);
// Make sure CheckForUnusedVars works on scope1 (we didn't mark the value as
// used in the previous step).
Err err;
EXPECT_FALSE(mutable_scope1.CheckForUnusedVars(&err));
mutable1_result =
mutable_scope2.GetMutableValue(kOnMutable1, Scope::SEARCH_NESTED, true);
EXPECT_TRUE(mutable1_result);
err = Err();
EXPECT_TRUE(mutable_scope1.CheckForUnusedVars(&err));
// Test reading a value from scope 2.
Value* mutable2_result =
mutable_scope2.GetMutableValue(kOnMutable2, Scope::SEARCH_NESTED, true);
ASSERT_TRUE(mutable2_result);
EXPECT_TRUE(*mutable2_result == value);
}
TEST(Scope, RemovePrivateIdentifiers) {
TestWithScope setup;
setup.scope()->SetValue("a", Value(nullptr, true), nullptr);
setup.scope()->SetValue("_b", Value(nullptr, true), nullptr);
setup.scope()->RemovePrivateIdentifiers();
EXPECT_TRUE(setup.scope()->GetValue("a"));
EXPECT_FALSE(setup.scope()->GetValue("_b"));
}