blob: bf11998666d2d3cc0f5b3ced743585f6c517ab7d [file] [log] [blame]
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/testing/config.gni")
import("//build/testing/host_test.gni")
import("//build/testing/host_test_data.gni")
# Declares a golden test.
#
# This template creates a test to verify that certain generated files match
# golden copies checked into the source tree. When invoked with the --regen
# flag, the test instead overwrites the golden files with freshly generated
# copies, guaranteeing that the next test run will pass.
#
# Set the `bless_goldens` build arg to true to include `--regen` in the arguments
# to the test script.
#
# For simpler, single-file golden testing that executes during the build, see
# //build/testing/golden_file.gni.
#
# Example
# ```
# golden_test("foo_golden_tests") {
# goldens_dir = "goldens"
# deps = [ ":generate_json" ]
# reformat_goldens_bin = "path/to/my/json/formatter"
# reformat_goldens_args = [ "--pretty" ]
# reformat_goldens_inputs = [ "path/to/my/json/formatter.config" ]
# entries = [
# {
# golden = "out.json.golden"
# generated = "$target_gen_dir/foo_out.json"
# },
# ]
# }
# ```
#
# Setup
# ```
# $ $EDITOR BUILD.gn # add the example code above
# $ mkdir goldens
# $ touch goldens/goldens.txt
# $ fx test foo_golden_tests -- --regen
# Regenerating goldens in /path/to/your/goldens
# ...
# $ fx test foo_golden_tests
# ...
# PASS
# ```
#
# Parameters
#
# goldens_dir (required)
# Directory of golden files, relative to the invoking BUILD.gn file. Must
# contain a goldens.txt file. Each file path listed in goldens.txt must
# exist, otherwise a gen-time error is produced.
# Type: path
#
# entries (required)
# List of scopes, each with `golden` and `generated` fields. The `golden`
# field is a file path relative to `goldens_dir`. The `generated` field is
# an absolute path to the corresponding generated file, which must be
# produced by one of the targets in `deps`.
# Type: list(scope)
#
# reformat_goldens_bin (optional)
# A command that will reformat from stdin to stdout.
# The command will be invoked for each golden file checked into the tree
# before comparing it to the newly generated version. This allows changes
# to source formatting tools to not cause goldens tests to fail.
# Type: string
#
# reformat_goldens_args (optional)
# A list of args for invoking `reformat_goldens_bin`.
# Type: list(string)
# Default: empty
#
# reformat_goldens_inputs (optional)
# Inputs the reformat_goldens_command uses, this should typically include
# a config file the formatter uses, if any. This makes sure when the config
# changes, this template can correctly rerun the reformat action.
# Type: list(string)
# Default: empty
#
# deps (required)
# visibility
#
template("golden_test") {
assert(is_host, "The golden_test template can only be used on host")
assert(defined(invoker.goldens_dir))
assert(defined(invoker.entries))
assert(defined(invoker.deps))
copied_goldens_dir = "${target_gen_dir}/${target_name}"
manifest_file = "${target_gen_dir}/${target_name}_manifest.json"
# Read the checked-in list of golden files.
goldens_list = read_file("${invoker.goldens_dir}/goldens.txt", "list lines")
assert(goldens_list ==
process_file_template(goldens_list, "{{source_file_part}}"),
"Subdirectories are not allowed in goldens.txt")
# Copy the goldens.txt list and make it available to the test at runtime.
target_test_data_goldens_list = "${target_name}_test_data_goldens_list"
host_test_data(target_test_data_goldens_list) {
visibility = [ ":*" ]
sources = [ "${invoker.goldens_dir}/goldens.txt" ]
outputs = [ "${copied_goldens_dir}/goldens.txt" ]
}
# Reformat or copy the goldens from the source directory by piping each
# golden file through `reformat_goldens`.
target_reformat_goldens = "${target_name}_reformat_goldens"
if (goldens_list == []) {
# No-op target to avoid breaking `gn gen` when goldens.txt is empty
# (action_foreach is not allowed to have zero source files).
group(target_reformat_goldens) {
}
not_needed(invoker,
[
"reformat_goldens_bin",
"reformat_goldens_args",
"reformat_goldens_inputs",
])
} else {
action_foreach(target_reformat_goldens) {
testonly = true
visibility = [ ":*" ]
# Always copy & format the goldens when the generated files change to avoid
# comparing stale goldens.
deps = invoker.deps
script = "//build/testing/reformat_goldens.py"
sources = rebase_path(goldens_list, ".", invoker.goldens_dir)
inputs = []
if (defined(invoker.reformat_goldens_inputs)) {
inputs += invoker.reformat_goldens_inputs
}
outputs = [ "${copied_goldens_dir}/{{source_file_part}}" ]
output_dir = rebase_path(copied_goldens_dir, root_build_dir)
args = [
"--source",
"{{source}}",
"--output",
"${output_dir}/{{source_file_part}}",
]
if (defined(invoker.reformat_goldens_bin)) {
inputs += [ invoker.reformat_goldens_bin ]
args += [
"--formatter",
rebase_path(invoker.reformat_goldens_bin, root_build_dir),
]
if (defined(invoker.reformat_goldens_args)) {
args += [ "--" ] + invoker.reformat_goldens_args
}
} else {
assert(
!defined(invoker.reformat_goldens_args),
"reformat_golden_args is only valid if a reformat_goldens_bin is specified")
}
}
}
# Make the copy of the goldens available to the test at runtime.
target_test_data_goldens = "${target_name}_test_data_goldens"
host_test_data(target_test_data_goldens) {
visibility = [ ":*" ]
deps = [ ":${target_reformat_goldens}" ]
sources = []
foreach(golden, goldens_list) {
sources += [ "${copied_goldens_dir}/${golden}" ]
}
}
# Generate the manifest file.
target_generate_manifest = "${target_name}_generate_manifest"
generated_file(target_generate_manifest) {
visibility = [ ":*" ]
testonly = true
contents = {
test_goldens_dir = rebase_path(copied_goldens_dir, root_build_dir)
regen_goldens_dir = rebase_path(invoker.goldens_dir, root_build_dir)
entries = []
}
foreach(entry, invoker.entries) {
assert(get_path_info(entry.golden, "extension") == "golden",
"Golden filename '${entry.golden}' does not end in '.golden'")
contents.entries += [
{
golden = entry.golden
generated = rebase_path(entry.generated, root_build_dir)
},
]
}
outputs = [ manifest_file ]
output_conversion = "json"
}
# Allow the test to access the manifest file and generated files.
target_generated_files = "${target_name}_generated_files"
host_test_data(target_generated_files) {
visibility = [ ":*" ]
sources = [ manifest_file ]
foreach(entry, invoker.entries) {
sources += [ entry.generated ]
}
deps = [ ":${target_generate_manifest}" ] + invoker.deps
}
# Generate a test target, so that this test can be executed with `fx test`.
host_test(target_name) {
forward_variables_from(invoker, [ "visibility" ])
binary_path = "${root_out_dir}/golden-util"
args = [
"--manifest",
rebase_path(manifest_file, root_build_dir),
]
if (bless_goldens) {
args += [ "--regen" ]
}
deps = [
":${target_generated_files}",
":${target_test_data_goldens_list}",
":${target_test_data_goldens}",
"//tools/golden-util",
]
}
}