blob: 011c233601c337e26098ef94895eaa93b9632041 [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")
# Defines a set of golden file comparisons to be executed during the build.
# Fails if one of the provided `golden`'s contents is not equal to the
# associated `candidate`'s.
#
# If the `update_goldens` build arg is true, the goldens will be updated with
# the candidate files rather than failing the action.
#
# Parameters:
#
# * comparisons
# - Required: The list of golden file comparisons to perform.
# - Type: list(scope)
#
# Each scope contains:
# * golden
# - Required: The golden file against which to check.
# - Type: path
#
# * candidate
# - Required: The file under test.
# - Type: path
#
# * formatter
# - Optional: A formatting specification. This may be used to format the
# goldens before comparing with the candidates, making the diffs less
# sensitive to such changes.
# - Type: scope
#
# The scope contains:
#
# * script
# - Required: The path to the formatting executable. (Called "script"
# because of the `action()` precedent and the fact that "executable"
# is a GN keyword). The formatter takes a file via stdin and outputs
# its contents to stdout.
# - Type: path
#
# * args
# - Optional: The list of arguments to pass to the formatter. Any
# contained paths must be given relative to `root_build_dir`.
# - Type: list(string)
#
# * extensions
# - Optional: The list of file extensions to which the formatter should
# be restricted in application. An empty list is taken to mean that
# the formatter should be applied to every golden.
# - Type: list(string)
# - Default: []
#
# * inputs
# - Optional: Any additional files that serve as inputs to the
# formatting invocation. The formatter itself need not be listed here.
# - Type: list(string)
#
# * deps
# - Optional: Any dependencies of the formatter.
# - Type: list(label)
#
# * warn_on_changes
# - Optional: If true, mismatches are treated as warnings rather than errors.
# - Type: bool
# - Default: false
#
# * visibility, testonly, deps, public_deps
# - Optional: Usual GN meanings.
#
template("golden_files") {
forward_variables_from(invoker,
[
"comparisons",
"formatter",
])
assert_context = "golden_files(\"$target_name\")"
assert(defined(comparisons) && comparisons != [],
"$assert_context: `comparisons` is required")
if (defined(formatter)) {
assert(
defined(formatter.script),
"$assert_context: `formatter.script` is required if `formatter` is supplied")
# Formatted versions of the goldens will be written to this directory and
# ultimately supplied to the diff action below, allowing for a degree of
# format-insensitivity.
formatted_goldens_dir = "$target_gen_dir/formatted-goldens"
}
# In the actions below, we purposefully do not register the golden and
# candidate files as inputs; instead we record them within a depfile. Further
# the script will proactively create a blank golden file if the supplied one
# does not yet exist. These two measures allow (1) a user to not have to
# tediously create the files themselves when adding a new `golden_files()`
# target, and (2) ninja to execute this action without complaint before the
# files exist.
main_target = target_name
comparison_json_target = "_golden_files.$target_name.generated_file"
# We build up variations of `comparisons` to construct the JSON file we pass
# to verify_golden_files.py and for the metadata we'll attach to the action.
comparison_json_contents = []
file_metadata = []
verify_deps = []
foreach(comparison, comparisons) {
assert(defined(comparison.golden),
"$assert_context: no `golden` given in comparison: $comparison")
assert(defined(comparison.candidate),
"$assert_context: no `candidate` given in comparison: $comparison")
# Forward the rest to ensure that nothing else was defined, in which case
# GN will provide an "unused" error.
forward_variables_from(comparison,
"*",
[
"golden",
"candidate",
])
source_relative_golden = rebase_path(comparison.golden, "//")
golden = rebase_path(comparison.golden, root_build_dir)
candidate = rebase_path(comparison.candidate, root_build_dir)
formatted_golden = "" # Clear from previous iteration.
if (defined(formatter)) {
extensions = [] # Clear from previous iteration.
if (defined(formatter.extensions)) {
extensions = formatter.extensions
}
golden_ext = get_path_info(golden, "extension")
if (extensions + [ golden_ext ] - [ golden_ext ] != extensions ||
extensions == []) {
format_golden_target = "_golden_files.$target_name.format.$golden"
formatted_golden = "$formatted_goldens_dir/$source_relative_golden"
action(format_golden_target) {
forward_variables_from(invoker, [ "testonly" ])
visibility = [ ":$main_target" ]
depfile = "$target_gen_dir/$target_name.d"
outputs = [ formatted_golden ]
inputs = [ formatter.script ]
if (defined(formatter.inputs)) {
inputs += formatter.inputs
}
forward_variables_from(invoker,
[
"deps",
"public_deps",
])
if (defined(formatter.deps)) {
if (!defined(deps)) {
deps = []
}
deps += formatter.deps
}
script = "//build/testing/format_golden.sh"
args = [
rebase_path(depfile, root_build_dir),
golden,
rebase_path(formatted_golden, root_build_dir),
rebase_path(formatter.script, root_build_dir),
]
if (defined(formatter.args)) {
args += formatter.args
}
}
verify_deps += [ ":$format_golden_target" ]
}
}
comparison_json_contents += [
{
golden = golden
candidate = candidate
# We don't want to supply the formatted golden under the `golden` key
# as the diff script needs to know the original location in order to
# auto-update it when a diff is detected.
if (formatted_golden != "") {
formatted_golden = rebase_path(formatted_golden, root_build_dir)
}
},
]
file_metadata += [
{
golden = source_relative_golden
candidate = candidate
},
]
}
comparison_json = "$target_gen_dir/$target_name.comparisons.json"
generated_file(comparison_json_target) {
output_conversion = "json"
contents = comparison_json_contents
outputs = [ comparison_json ]
}
verify_deps += [ ":$comparison_json_target" ]
action(main_target) {
forward_variables_from(invoker,
[
"visibility",
"testonly",
"deps",
"public_deps",
])
if (!defined(deps)) {
deps = []
}
deps += verify_deps
script = "//build/testing/verify_golden_files.py"
stamp_file = "$target_gen_dir/$target_name.verified"
depfile = "${stamp_file}.d"
inputs = [ comparison_json ]
outputs = [ stamp_file ]
args = [
"--depfile",
rebase_path(depfile, root_build_dir),
"--stamp-file",
rebase_path(stamp_file, root_build_dir),
"--comparisons",
rebase_path(comparison_json, root_build_dir),
"--label",
get_label_info(":${target_name}", "label_with_toolchain"),
"--source-root",
rebase_path("//", root_build_dir),
]
if (update_goldens) {
args += [ "--bless" ]
}
warn_on_changes =
defined(invoker.warn_on_changes) && invoker.warn_on_changes
if (warn_on_changes) {
args += [ "--warn" ]
}
metadata = {
# Metadata for //:golden_files.
golden_files = [
{
name = target_name
label = get_label_info(":$target_name", "label_with_toolchain")
stamp = rebase_path(stamp_file, root_build_dir)
files = file_metadata
},
]
}
}
}