chromium/chromecast/build/tests/cast_test.gni

# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# This file contains templates which are meant to simplify building and
# running test binaries with the Chromecast build infrastructure. See
# documentation above each template for specific use.
#
# Example Usage
#
#   # This is a standard test() template from //testing/test.gni. This generates
#   # a binary called foo_unittests.
#   test("foo_unittests") {
#     sources = [ "foo_unittest.cc" ]
#
#     deps = [
#       ":foo",
#       "//testing/gtest",
#       "//testing/gmock",
#     ]
#   }
#
#   # And another standard test() target, which generates bar_unittests
#   test("bar_unittests") {
#     sources = [ "bar_unittest.cc" ]
#
#     deps = [ ... ]
#   }
#
#   # This is an organizational target. This cannot be built directly.
#   cast_test_group("cast_tests") {
#     tests = [
#       ":bar_unittests",
#       "//path/to:foo_unittests",
#     ]
#   }
#
#   # Here is another cast_test_group target which builds a bunch of other
#   # test binaries, and wants to apply some filters.
#   cast_test_group("external_tests") {
#     filters = []
#     tests = [
#       "//path/to/widget:widget_unittests",
#       "//even/more/foo:foo_unittests",
#     ]
#
#     widget_unittests_filter = {
#       test_name = "widgets_unittests"
#       gtest_includes = [ "WidgetTest.*" ]
#       gtest_excludes = [ "WidgetTest.TestToBeFilteredOut",
#                          "WidgetTest.MoreTestsToFilterOut" ]
#       args = [ "--extra_arg",
#                "--another_arg", ]
#     }
#     filters += [ widget_unittests_filter ]
#   }
#
#   # Build this to create build and run lists for bar and foo tests.
#   cast_test_group_list("cast_test_lists") {
#     test_groups = [
#       ":cast_tests",
#       ":external_tests",
#     ]
#
#     build_list_paths = "$root_out_dir/path/to/test/build_list.txt"
#     run_list_path = "$root_out_dir/path/to/list/run_list.txt"
#     runtime_deps_path = "$root_out_dir/path/to/list/runtime_deps.json"
#   }

import("//chromecast/chromecast.gni")
import("//testing/test.gni")

declare_args() {
  # Set this true to build all tests in a cast_test_group_list by default. This
  # can be overriden in each cast_test_group_list instance by setting
  # |build_tests|.
  build_cast_tests_by_default = true
}

# Do not allow mixing of gtests and junit tests. All gtests must go into one
# directory, while all junit tests must go into another directory.
_gtest_gen_dir = "$root_gen_dir/chromecast/tests"
_junit_gen_dir = "$root_gen_dir/chromecast/junit"

# A group of test executables. Including test targets in this group makes it
# possible for Chromecast build infrastructure to build and run them with
# filters. To accomplish this, it defines two actions, one which generates a
# build list of all |tests|, and one which generates a run list of all |tests|.
# It also creates a group with dependencies on each test, to ensure that they
# are valid targets. Do not build these targets. Build cast_test_group_list
# instead.
#
# Parameters
#   tests (required)
#       A list of test targets included in the assembly. Do not include any
#       other type of target. Each target's name must match the name of the
#       executable it builds.
#
#   filters (optional)
#        A list of scopes, where each scope has the has the follow variables:
#          test_name (required): The name of the test executable. This must
#              correspond to a test in |tests|.
#          gtest_includes (optional): A list of gtest filter pattern strings.
#              Only tests that match one of the positive patterns (if provided)\
#              will be run.
#              Example: gtest_includes = [ "Foo.*", "Bar.Test1", "Bar.Test2" ]
#              will get translated to "--gtest_filter=Foo.*:Bar.Test1:Bar.Test2"
#          gtest_excludes (optional): A list of gtest filter pattern strings.
#              Only tests that do not match any of the negative patterns
#              (if provided) will be run.
#              Example: gtest_excludes = [ "Baz.*", "Biz.Test" ]
#              will get translated to "--gtest_filter=-Baz.*:Biz.Test1"
#          args (optional): A list of additional args to pass to the test.
#        Please see //chromecast/tools/build/generate_test_lists.py for more
#        information.
#        If |filters| is not defined, no filters are applied.
#
#   priority (optional)
#       A string which takes any single-digit integer bewtween "1" and "9",
#       inclusive. Assign this to prioritize filters applied by other
#       cast_test_groups, where a higher number trumps a lower number.
#       If not assigned, priority defaults to "1", the lowest priority.
#
#   test_type (optional)
#       A string, which must be either "junit" or "gtest". If not defined,
#       defaults to "gtest".
#
template("cast_test_group") {
  assert(defined(invoker.tests),
         "$target_name needs 'tests' listing the test() targets")

  _test_type = "gtest"
  if (defined(invoker.test_type)) {
    assert(invoker.test_type == "gtest" || invoker.test_type == "junit")
    _test_type = invoker.test_type
  }

  if (_test_type == "gtest") {
    _shared_dir = _gtest_gen_dir
  } else if (_test_type == "junit") {
    _shared_dir = _junit_gen_dir
  }

  # If a set of filters has not been defined, use the empty list.
  _filters = []
  if (defined(invoker.filters)) {
    foreach(filter, invoker.filters) {
      assert(defined(filter.test_name), "A filter must have a test_name.")
      _test_name = filter.test_name

      _gtest_include_filter = ""
      if (defined(filter.gtest_includes)) {
        foreach(include, filter.gtest_includes) {
          _gtest_include_filter = "${_gtest_include_filter}:${include}"
        }
      }

      _gtest_exclude_filter = ""
      if (defined(filter.gtest_excludes)) {
        foreach(exclude, filter.gtest_excludes) {
          _gtest_exclude_filter = "${_gtest_exclude_filter}:${exclude}"
        }
      }

      _gtest_filter = ""
      if (_gtest_include_filter != "" || _gtest_exclude_filter != "") {
        _gtest_filter += "--gtest_filter="
        if (_gtest_include_filter != "") {
          _gtest_filter += _gtest_include_filter
        }
        if (_gtest_exclude_filter != "") {
          _gtest_filter += "-" + _gtest_exclude_filter
        }
      }

      _args = ""
      if (defined(filter.args)) {
        foreach(arg, filter.args) {
          _args = "${_args} ${arg}"
        }
      }

      # Only add filters that actually do something, otherwise the
      # generate script will get confused.
      if (_args != "" || _gtest_filter != "") {
        _filters += [ "${_test_name} ${_args} ${_gtest_filter}" ]
      }
    }
  }

  # If priority has not been set, set the priority to "1", the lowest priority.
  _priority = "1"
  if (defined(invoker.priority)) {
    _priority = invoker.priority
  }

  # Assert that |_priority| is an integer between "1" and "9", inclusive.
  assert(_priority == "1" || _priority == "2" || _priority == "3" ||
         _priority == "4" || _priority == "5" || _priority == "6" ||
         _priority == "7" || _priority == "8" || _priority == "9")

  # This will be the prefix of each output file.
  _output_prefix = "$_shared_dir/$_priority-$target_name"

  # Create a list of all the target names. These must correspond to the name of
  # the test binary.
  _test_names = []
  foreach(_test, invoker.tests) {
    _test_names += [ get_label_info(_test, "name") ]
  }

  # Create a dummy target so that we can get the runtime deps for each test
  # and output them into a file. The runtime deps include all of the data files,
  # data directories, and shared libraries that a test needs in order to run.
  foreach(_test, invoker.tests) {
    _test_name = get_label_info(_test, "name")
    group(_test_name + "_cast_runtime_deps") {
      testonly = true
      data_deps = [ _test ]
      write_runtime_deps =
          "${_shared_dir}/runtime_deps/${_test_name}_runtime_deps.txt"
    }
  }

  # This action generates a list of target names to build and run. It will be
  # depended upon by the "pack_build" action of the cast_test_group_list
  # instance which depends on this cast_test_group.
  action(target_name + "_create_list") {
    script = "//chromecast/tools/build/generate_test_lists.py"

    outputs = [ "$_output_prefix.tests" ]

    args = [
      "-o",
      rebase_path("$_output_prefix.tests", root_build_dir),
      "create_list",
    ]

    args += _test_names

    deps = []
    if (defined(invoker.deps)) {
      foreach(_dep, invoker.deps) {
        deps += [ _dep + "_create_list" ]
      }
    }
  }

  # This action generates a list of test filters, which will have a priority
  # [1-9]. This will be depended upon by the "pack_run" action of the
  # cast_test_group_list which depends on this group.
  action(target_name + "_filters") {
    script = "//chromecast/tools/build/generate_test_lists.py"

    outputs = [ "$_output_prefix.filters" ]

    args = [
      "-o",
      rebase_path("$_output_prefix.filters", root_build_dir),
      "create_list",
    ]

    args += _filters

    deps = []
    if (defined(invoker.deps)) {
      foreach(_dep, invoker.deps) {
        deps += [ _dep + "_filters" ]
      }
    }
  }

  # This target allows us to reference each test as a fully-qualified GN path,
  # to ensure that each path is correct. If a test does not exist, gives a
  # helpful error message at the line it is included. Do not build this target
  # directly.
  group(target_name + "_build_tests") {
    testonly = true
    deps = invoker.tests
    if (defined(invoker.deps)) {
      foreach(_dep, invoker.deps) {
        deps += [ _dep + "_build_tests" ]
      }
    }
  }
}

# This template runs a script which generates lists of test to be built and run.
#
# Parameters
#   test_groups (required)
#       The cast_test_group() targets for which this binary is to be created.
#       The targets referenced here must be cast_test_group targets, or buiding
#       this target will fail.
#
#   build_list_path (required)
#       The absolute filepath of the output file which will hold the list of
#       tests to be built.
#
#   run_list_path (required)
#       The absolute filepath of the output file which will hold the list of
#       tests to be run, each with filters assigned by cast_groups.
#
#   runtime_deps_path (required)
#       The absolute filepath of the output file which will hold the json dict
#       of runtime dependencies for each test to be run.
#
#   additional_options (optional)
#       Options which are passed to the python script, and applied to every test
#
#   build_tests (optional)
#       If true, all of the tests included in |test_groups| will be built as
#       dependencies of this target. If false, only the lists will be generated.
#       If not explicitly set, this defaults to the value of
#       |build_cast_tests_by_default|. Note that if this is set to true,
#       the test targets will be built after all the lists are generated.
#
#   test_type (optional)
#       A string, which must be either "junit" or "gtest". If not defined,
#       defaults to "gtest".
#
template("cast_test_group_list") {
  assert(defined(invoker.test_groups), "$target_name needs 'test_groups'")
  assert(defined(invoker.run_list_path), "$target_name needs 'run_list_path'")
  assert(defined(invoker.build_list_path),
         "$target_name needs 'build_list_path'")
  assert(defined(invoker.runtime_deps_path),
         "$target_name needs 'runtime_deps_path'")

  _pack_build_action = target_name + "_pack_build"

  _test_type = "gtest"
  if (defined(invoker.test_type)) {
    assert(invoker.test_type == "gtest" || invoker.test_type == "junit")
    _test_type = invoker.test_type
  }

  if (_test_type == "gtest") {
    _shared_dir = _gtest_gen_dir
  } else if (_test_type == "junit") {
    _shared_dir = _junit_gen_dir
  }

  # Add the run list to runtime data dependencies
  data = [
    invoker.run_list_path,
    invoker.runtime_deps_path,
  ]

  # Generate a list of the "create_list" actions for each group. These will be
  # depended upon to ensure they're run before the "pack_build" step.
  _build_actions = []
  foreach(_test_group, invoker.test_groups) {
    _build_actions += [ _test_group + "_create_list" ]
  }

  # Generate a list of the "filter" actions for each group. These will be
  # depended upon to ensure they're run before the "pack_run" step.
  _filter_actions = []
  foreach(_test_group, invoker.test_groups) {
    _filter_actions += [ _test_group + "_filters" ]
  }

  # Decide whether tests are built as dependencies.
  _build_cast_tests = build_cast_tests_by_default
  if (defined(invoker.build_tests)) {
    _build_cast_tests = invoker.build_tests
  }

  # Generate a list of the groups of targets, so that they can be depended upon
  # by the "pack_run" step and built when this target is invoked.
  if (_build_cast_tests) {
    _build_test_targets = []
    foreach(_test_group, invoker.test_groups) {
      _build_test_targets += [ _test_group + "_build_tests" ]
    }
  }

  # The "pack_build" step. This step looks in the common folder for files with
  # the ".tests" extenstion, collecting these and packing them into an output
  # file.  The steps which create these files are depeneded upon, to ensure
  # they're run before this step. Do not invoke this target directly.
  action(_pack_build_action) {
    script = "//chromecast/tools/build/generate_test_lists.py"

    outputs = [ invoker.build_list_path ]

    args = [
      "-o",
      rebase_path(invoker.build_list_path, root_build_dir),
      "-t",
      rebase_path(_shared_dir, root_build_dir),
      "pack_build",
    ]

    deps = _build_actions
  }

  # The "pack_run" step. This step looks in the common folder for files with
  # the ".tests" and ".filters" extensions, creating a script of tests to run,
  # with filters and priorities. See
  # //chromecast/tools/build/generate_test_lists.py for more information.
  # Note that this target takes the name of the invoker, such that invoking the
  # target runs this step.
  action(target_name) {
    testonly = true

    script = "//chromecast/tools/build/generate_test_lists.py"

    outputs = [
      invoker.run_list_path,
      invoker.runtime_deps_path,
    ]

    args = [
      "-o",
      rebase_path(invoker.run_list_path, root_build_dir),
      "-d",
      rebase_path(invoker.runtime_deps_path, root_build_dir),
      "-t",
      rebase_path(_shared_dir, root_build_dir),
      "pack_run",
    ]

    # Add addtional options if they have been set.
    if (defined(invoker.additional_options)) {
      args += [ "-a" ]
      args += invoker.additional_options
    }

    # Depend first on the "pack_build" step, so that the build lists are created.
    deps = [ ":$_pack_build_action" ]

    # Next, depend on the filter steps, such that they are created before this
    # script executes.
    deps += _filter_actions

    # If |_build_cast_tests| has been set to true, depend on the testing targets
    # so that the tests are built.
    if (_build_cast_tests) {
      deps += _build_test_targets
    }

    if (chromecast_branding != "public") {
      deps += [ "//chromecast/internal:cast_test_checker" ]
    }
  }
}

# This template creates a tar.gz file with test dependencies.
#
# Parameters
#   output (required)
#       The absolute path to the tar.gz file to be created.
#
#   deps_list_path (required)
#       Absolute path to runtime dependencies file. This has to point to the
#       same file runtime_deps_path of cast_test_group_list is pointing to.
#
#   exclude_deps (optional)
#       gn list of paths to filter out from the dependencies list.
#       This is used to remove folders with huge files that are not needed.
#
#   additional_deps (optional)
#       gn list of paths to be added to the list of dependencies
#       before creating the tar.gz file.
#
# This template has to be used in combination with cast_test_group_list
# that generates the runtime dependencies file.
#
# Example usage:
#
# cast_test_group_list("chromecast_test_lists") {
#  build_list_path = "$root_out_dir/tests/build_test_list.txt"
#  runtime_deps_path = "$root_out_dir/tests/runtime_deps.json"
#  run_list_path = "$root_out_dir/tests/run_test_list.txt"
#  test_groups = [ ":chromecast_test_group" ]
# }
#
# cast_test_deps_archive("chromecast_test_deps_archive") {
#  output = "$root_out_dir/test_deps.tar.gz"
#  deps_list_path = "$root_out_dir/tests/runtime_deps.json"
#  exclude_deps = [
#    "exe.unstripped",
#    "lib.unstripped"
#  ]
#  additional_deps = [
#    "tests",
#  ]
#  deps = [
#    ":chromecast_test_lists"
#  ]
# }
#
template("cast_test_deps_archive") {
  assert(defined(invoker.output), "$target_name needs 'output'")
  assert(defined(invoker.deps_list_path), "$target_name needs 'deps_list_path'")

  deps = invoker.deps

  action(target_name) {
    testonly = true

    script = "//chromecast/tools/build/package_test_deps.py"
    outputs = [ invoker.output ]

    # Generate a comma separated list of filters.
    _exclude_deps = ""
    foreach(_exclude_dep, invoker.exclude_deps) {
      _exclude_deps += "," + _exclude_dep
    }

    # Generate a comma separated list of includes.
    _additional_deps = ""
    foreach(_additional_dep, invoker.additional_deps) {
      _additional_deps += "," + _additional_dep
    }

    args = [
      "--output",
      rebase_path(invoker.output, root_build_dir),
      "--deps_list_path",
      rebase_path(invoker.deps_list_path, root_build_dir),
      "--exclude_deps",
      _exclude_deps,
      "--additional_deps",
      _additional_deps,
    ]
  }
}