blob: 838eb3e30b1ccdd9500de807d076392a576850fd [file] [log] [blame]
// Copyright 2016 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 "base/strings/string_util.h"
#include "gn/c_tool.h"
#include "gn/functions.h"
#include "gn/rust_tool.h"
#include "gn/test_with_scheduler.h"
#include "gn/test_with_scope.h"
#include "util/test/test.h"
using FunctionToolchain = TestWithScheduler;
TEST_F(FunctionToolchain, NoArguments) {
TestWithScope setup;
// Check that creating a toolchain with no name reports an error.
{
TestParseInput input(R"(toolchain() {})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << err.message();
}
// Check that creating a toolchain with too many arguments is an error.
{
TestParseInput input(R"(toolchain("too", "many", "arguments") {})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << err.message();
}
}
// Substitute all instances of |src| in |input| with |dst|.
static std::string StrSubstitute(std::string_view input,
std::string_view src,
std::string_view dst) {
std::string result(input);
base::ReplaceSubstringsAfterOffset(&result, 0, src, dst);
return result;
}
TEST_F(FunctionToolchain, RuntimeOutputs) {
// A list of tool types which support runtime_outputs.
const char* kGoodToolTypes[] = {
CTool::kCToolLink, CTool::kCToolSolink, CTool::kCToolSolinkModule,
RustTool::kRsToolBin, RustTool::kRsToolDylib, RustTool::kRsToolCDylib,
RustTool::kRsToolMacro,
};
for (const auto& tool_type : kGoodToolTypes) {
TestWithScope setup;
{
// These runtime outputs are a subset of the outputs so are OK.
const char kInput[] = R"(
toolchain("good") {
tool("{{name}}") {
command = "{{name}}"
outputs = [ "foo" ]
runtime_outputs = [ "foo" ]
}
})";
std::string input_str = StrSubstitute(kInput, "{{name}}", tool_type);
TestParseInput input(input_str);
ASSERT_FALSE(input.has_error()) << "INPUT [" << input_str << "]";
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
// It should have generated a toolchain.
ASSERT_EQ(1u, setup.items().size());
const Toolchain* toolchain = setup.items()[0]->AsToolchain();
ASSERT_TRUE(toolchain);
// The toolchain should have a link tool with the two outputs.
const Tool* link = toolchain->GetTool(tool_type);
ASSERT_TRUE(link) << tool_type;
ASSERT_EQ(1u, link->outputs().list().size());
EXPECT_EQ("foo", link->outputs().list()[0].AsString());
ASSERT_EQ(1u, link->runtime_outputs().list().size());
EXPECT_EQ("foo", link->runtime_outputs().list()[0].AsString());
}
// This one is not a subset so should throw an error.
{
const char kInput[] = R"(
toolchain("bad") {
tool("{{name}}") {
command = "{{name}}"
outputs = [ "foo" ]
runtime_outputs = [ "bar" ]
}
})";
std::string input_str = StrSubstitute(kInput, "{{name}}", tool_type);
TestParseInput input(input_str);
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << "INPUT [" << input_str << "]";
}
}
// A list of tool types which do not support runtime_outputs.
const char* const kBadToolTypes[] = {
CTool::kCToolCc, CTool::kCToolCxx, CTool::kCToolCxxModule,
CTool::kCToolObjC, CTool::kCToolObjCxx, CTool::kCToolRc,
CTool::kCToolAsm, CTool::kCToolSwift, CTool::kCToolAlink,
RustTool::kRsToolRlib, RustTool::kRsToolStaticlib,
};
for (const auto& tool_type : kBadToolTypes) {
TestWithScope setup;
{
// These runtime outputs are a subset of the outputs so are OK.
// But these tool names do not support runtime outputs!
const char kInput[] = R"(
toolchain("unsupported") {
tool("{{name}}") {
command = "{{name}}"
outputs = [ "foo" ]
runtime_outputs = [ "foo" ]
}
})";
std::string input_str = StrSubstitute(kInput, "{{name}}", tool_type);
TestParseInput input(input_str);
ASSERT_FALSE(input.has_error()) << "INPUT [" << input_str << "]";
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << "INPUT [" << input_str << "]";
ASSERT_EQ(err.message(), "This tool specifies runtime_outputs.")
<< tool_type;
}
}
}
TEST_F(FunctionToolchain, Rust) {
TestWithScope setup;
// These runtime outputs are a subset of the outputs so are OK.
{
TestParseInput input(
R"(toolchain("rust") {
tool("rust_bin") {
command = "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin {{rustflags}} -o {{output}} {{externs}} {{source}}"
description = "RUST {{output}}"
}
})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
// It should have generated a toolchain.
ASSERT_EQ(1u, setup.items().size());
const Toolchain* toolchain = setup.items()[0]->AsToolchain();
ASSERT_TRUE(toolchain);
const Tool* rust = toolchain->GetTool(RustTool::kRsToolBin);
ASSERT_TRUE(rust);
ASSERT_EQ(rust->command().AsString(),
"{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin "
"{{rustflags}} -o {{output}} {{externs}} {{source}}");
ASSERT_EQ(rust->description().AsString(), "RUST {{output}}");
}
}
TEST_F(FunctionToolchain, RustLinkDependAndRuntimeOutputs) {
TestWithScope setup;
// These runtime outputs are a subset of the outputs so are OK.
{
TestParseInput input(
R"(toolchain("good") {
tool("rust_dylib") {
command = "rust_dylib"
outputs = [ "interface", "lib", "unstripped", "stripped" ]
depend_output = "interface"
link_output = "lib"
runtime_outputs = [ "stripped" ]
}
})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
// It should have generated a toolchain.
ASSERT_EQ(1u, setup.items().size());
const Toolchain* toolchain = setup.items()[0]->AsToolchain();
ASSERT_TRUE(toolchain);
// The toolchain should have a link tool with the two outputs.
const Tool* link = toolchain->GetTool(RustTool::kRsToolDylib);
ASSERT_TRUE(link);
ASSERT_EQ(4u, link->outputs().list().size());
EXPECT_EQ("interface", link->outputs().list()[0].AsString());
EXPECT_EQ("lib", link->outputs().list()[1].AsString());
EXPECT_EQ("unstripped", link->outputs().list()[2].AsString());
EXPECT_EQ("stripped", link->outputs().list()[3].AsString());
ASSERT_EQ(1u, link->runtime_outputs().list().size());
EXPECT_EQ("stripped", link->runtime_outputs().list()[0].AsString());
const RustTool* rust_tool = link->AsRust();
ASSERT_TRUE(rust_tool);
EXPECT_EQ("interface", rust_tool->depend_output().AsString());
EXPECT_EQ("lib", rust_tool->link_output().AsString());
}
}
TEST_F(FunctionToolchain, Command) {
TestWithScope setup;
TestParseInput input(
R"(toolchain("missing_command") {
tool("cxx") {}
})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << err.message();
}
TEST_F(FunctionToolchain, CommandLauncher) {
TestWithScope setup;
TestParseInput input(
R"(toolchain("good") {
tool("cxx") {
command = "cxx"
command_launcher = "/usr/goma/gomacc"
}
})");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
// It should have generated a toolchain.
ASSERT_EQ(1u, setup.items().size());
const Toolchain* toolchain = setup.items()[0]->AsToolchain();
ASSERT_TRUE(toolchain);
// The toolchain should have a link tool with the two outputs.
const Tool* link = toolchain->GetTool(CTool::kCToolCxx);
ASSERT_TRUE(link);
EXPECT_EQ("/usr/goma/gomacc", link->command_launcher());
}