<commit_before>load("@io_bazel_rules_go//go:def.bzl", "go_test")

"""Macros for running acceptance tests."""

load("@rules_pkg//pkg:zip.bzl", "pkg_zip")

# acceptance_test_suite defines several targets that are useful with buildpacks acceptance tests.
# It defines a test target for each version, a test suite that runs all of the versioned targets,
# a cloudbuild build configuration, and tarball for submitting to cloudbuild. See below for more
# specifics on each of the targets.
#
# Given a name of 'gae_test' and versions value of [1.13, 1.14], it defines the following:
# * 1.13_gae_test: A test target with a version of 1.13.
# * 1.14_gae_test: A test target with a version of 1.14.
# * gae_test: A test suite target that aliases to [1.13_gae_test, 1.14_gae_test]. Useful for running
#   all versions of the tests.
# * gae_test_cloudbuild.yaml: A cloudbuild config which contains a step for running the tests
#   against 1.13 and a step for running against 1.14.
# * gae_test_cloudbuild.zip: A zip which can be used with 'gae_test_cloudbuild.yaml' to
#   submit a cloudbuild.
#
# To submit a cloudbuild with the go builder:
#   blaze build //third_party/gcp_buildpacks/builders/go/acceptance:gae_test_cloudbuild.zip
#   blaze build //third_party/gcp_buildpacks/builders/go/acceptance:gae_test_cloudbuild.yaml
#   gcloud builds submit \
#      blaze-bin/third_party/gcp_buildpacks/builders/go/acceptance/gae_test_cloudbuild.zip \
#      --config blaze-genfiles/third_party/gcp_buildpacks/builders/go/acceptance/gae_test_cloudbuild.yaml

def acceptance_test_suite(
        name,
        srcs,
        testdata,
        builder,
        structure_test_config = ":config.yaml",
        versions = None,
        args = None,
        deps = None,
        argsmap = None,
        **kwargs):
    """Macro to define an acceptance test.

    Args:
      name: the name of the test
      srcs: the test source files
      testdata: a build target for a directory containing sample test applications
      builder: a build target for a builder.tar to test
      structure_test_config: a build target for the structured container test's config file
      versions: a list of GOOGLE_RUNTIME_VERSIONS to test, these correspond to language versions
      args: additional arguments to be passed to the test binary beyond ones corresponding to the arguments to this function
      deps: additional test dependencies beyond the acceptance package
      argsmap: version specific arguments map where the key is the version and the value is a list of flags that will be passed to the acceptance test framework
      **kwargs: this argument captures all additional arguments and forwards them to the generated go_test rule
    """

    test_args = _build_args(args, name, testdata, builder, structure_test_config)
    data = _build_data(structure_test_config, builder, testdata)
    deps = _build_deps(deps)

    _build_tests(name, srcs, test_args, data, deps, versions, argsmap, **kwargs)
    _cloudbuild_targets(name, srcs, structure_test_config, builder, args, deps, versions, argsmap, testdata)

def _build_tests(name, srcs, args, data, deps, versions, argsmap, **kwargs):
    # if there are no versions passed in then create a go_test(...) rule directly without changing
    # the name of the test
    if versions == None:
        _new_go_test_for_single_version(name, srcs, args, data, deps, **kwargs)
    else:
        _new_go_test_for_versions(versions, name, srcs, args, data, deps, argsmap, **kwargs)

def _new_go_test_for_versions(versions, name, srcs, args, data, deps, argsmap, **kwargs):
    tests = []
    for v in versions:
        ver_name = v + "_" + name
        tests.append(ver_name)
        ver_args = list(args)
        ver_args.append("-runtime-version=" + v)

        if argsmap != None and argsmap.get(v) != None:
            for ak, av in argsmap[v].items():
                ver_args.append(ak + "=" + av)

        _new_go_test(ver_name, srcs, ver_args, data, deps, **kwargs)

    # Each of the go_test(...) rules generated by _new_go_test have a name that is hard for
    # developers to reference, for example, '//acceptance:gcp_test_3.1.416'. To make this
    # easy to use, generate a test suite with the name of the original build target, so
    # developers can reference simply '//acceptance:gcp_test'.
    native.test_suite(
        name = name,
        tests = tests,
    )
    _new_bin_filegroup_alias(name, tests[0])

def _new_go_test_for_single_version(name, srcs, args, data, deps, **kwargs):
    _new_go_test(name, srcs, args, data, deps, **kwargs)
    _new_bin_filegroup_alias(name, name)

def _new_go_test(name, srcs, args, data, deps, **kwargs):
    go_test(
        name = name,
        size = "enormous",
        srcs = srcs,
        args = args,
        data = data,
        tags = [
            "local",
        ],
        gc_linkopts = [],
        deps = deps,
        **kwargs
    )

def _new_bin_filegroup_alias(name, test_name):
    # The test binaries generated in this file have names such as '1.13_gae_test'. For external
    # consumers who wish to invoke the test binary directly, the following filegroup gives them
    # a static name that they can reference such as 'gae_test_bin'.
    native.filegroup(
        name = name + "_bin",
        srcs = [":" + test_name],
        testonly = 1,
    )

def _build_args(args, name, testdata, builder, structure_test_config):
    short_name = _remove_suffix(name, "_test")
    builder_name = _extract_builder_name(builder)

    if args == None:
        args = []
    else:
        # make a copy of the args list to prevent mutating the passed in value
        args = list(args)
    args.append("-test-data=$(location " + testdata + ")")
    args.append("-structure-test-config=$(location " + structure_test_config + ")")
    args.append("-builder-source=$(location " + builder + ")")
    args.append("-builder-prefix=" + builder_name + "-" + short_name + "-acceptance-test-")
    args.append("-runtime-name=" + builder_name)
    return args

def _build_data(structure_test_config, builder, testdata):
    return [
        structure_test_config,
        builder,
        testdata,
    ]

def _extract_builder_name(builder):
    # A builder target is a full google3 path, the name of the builder, and then :builder.tar, the following
    # extracts the name of the builder.
    builder_name = _remove_suffix(builder, ":builder.tar")
    builder_name = builder_name[builder_name.rindex("/") + 1:]
    return builder_name

def _build_deps(deps):
    if deps == None:
        deps = []
    else:
        # make a copy of the list to prevent mutating a shared 'deps' value declared in a BUILD file
        deps = list(deps)
    deps.append("//internal/acceptance")
    return deps

# Once bazel supports python 3.9, this function can be replaced with `value.removesuffix(suffix)`:
def _remove_suffix(value, suffix):
    if value.endswith(suffix):
        value = value[:-len(suffix)]
    return value

# _cloudbuild_targets builds two rules that can be used to run the acceptance tests in gcloud.
def _cloudbuild_targets(name, srcs, structure_test_config, builder, args, deps, versions, argsmap, testdata):
    bin_name = _build_cloudbuild_test_binary(name, srcs, deps)

    # this hack is to prevent the tarball from being built in bazel, as _build_testdata_target
    # makes use of Fileset which is not available in bazel.
    if testdata.startswith("//third_party/gcp_buildpacks"):
        _build_cloudbuild_zip(name, bin_name, structure_test_config, builder, testdata)
    _build_cloudbuild_config_target(name, bin_name, builder, args, versions, argsmap, testdata)

def _build_cloudbuild_zip(name, bin_name, structure_test_config, builder, testdata):
    testdata_fileset_name = _build_testdata_target(name, testdata)
    pkg_zip(
        name = name + "_cloudbuild",
        srcs = [
            bin_name,
            builder,
            testdata_fileset_name,
            structure_test_config,
        ],
        testonly = 1,
    )

def _build_cloudbuild_test_binary(name, srcs, deps):
    bin_name = name + "cloudbuild_bin"
    _new_go_test(bin_name, srcs, None, None, deps)
    return bin_name

# _build_testdata_target creates a Fileset target for the given testdata label. The reason to do
# this is our testdata is accessed via exports_files(...) which copies the testdata into writeable
# folders. The acceptance test suite relies on the the folders being writeable. However, most Bazel
# rules, including pkg_zip or pkg_tar, are not able to accept source files that are directories. It
# requires an explicit fileset to bring in a directory of files.
def _build_testdata_target(name, testdata):
    testdata_label = testdata[testdata.rfind(":") + 1:]
    fileset_name = name + "_" + testdata_label
    native.Fileset(
        name = fileset_name,
        out = name + "_generated/" + testdata_label,
        entries = [
            native.FilesetEntry(
                srcdir = testdata,
            ),
        ],
    )
    return fileset_name

def _build_cloudbuild_config_target(name, bin_name, builder, args, versions, argsmap, testdata):
    cloudbuild_config = _build_cloudbuild_config(name, bin_name, builder, args, versions, argsmap, testdata)
    native.genrule(
        name = name + "_cloudbuild_config",
        outs = [name + "_cloudbuild.yaml"],<commit_msg>Add an underscore to the name of the 'bin' file included in the cloudbuild zip. For example, 'gae_test_cloudbuild_bin' instead of 'gae_testcloudbuild_bin'<commit_after>load("@io_bazel_rules_go//go:def.bzl", "go_test")

"""Macros for running acceptance tests."""

load("@rules_pkg//pkg:zip.bzl", "pkg_zip")

# acceptance_test_suite defines several targets that are useful with buildpacks acceptance tests.
# It defines a test target for each version, a test suite that runs all of the versioned targets,
# a cloudbuild build configuration, and tarball for submitting to cloudbuild. See below for more
# specifics on each of the targets.
#
# Given a name of 'gae_test' and versions value of [1.13, 1.14], it defines the following:
# * 1.13_gae_test: A test target with a version of 1.13.
# * 1.14_gae_test: A test target with a version of 1.14.
# * gae_test: A test suite target that aliases to [1.13_gae_test, 1.14_gae_test]. Useful for running
#   all versions of the tests.
# * gae_test_cloudbuild.yaml: A cloudbuild config which contains a step for running the tests
#   against 1.13 and a step for running against 1.14.
# * gae_test_cloudbuild.zip: A zip which can be used with 'gae_test_cloudbuild.yaml' to
#   submit a cloudbuild.
#
# To submit a cloudbuild with the go builder:
#   blaze build //third_party/gcp_buildpacks/builders/go/acceptance:gae_test_cloudbuild.zip
#   blaze build //third_party/gcp_buildpacks/builders/go/acceptance:gae_test_cloudbuild.yaml
#   gcloud builds submit \
#      blaze-bin/third_party/gcp_buildpacks/builders/go/acceptance/gae_test_cloudbuild.zip \
#      --config blaze-genfiles/third_party/gcp_buildpacks/builders/go/acceptance/gae_test_cloudbuild.yaml

def acceptance_test_suite(
        name,
        srcs,
        testdata,
        builder,
        structure_test_config = ":config.yaml",
        versions = None,
        args = None,
        deps = None,
        argsmap = None,
        **kwargs):
    """Macro to define an acceptance test.

    Args:
      name: the name of the test
      srcs: the test source files
      testdata: a build target for a directory containing sample test applications
      builder: a build target for a builder.tar to test
      structure_test_config: a build target for the structured container test's config file
      versions: a list of GOOGLE_RUNTIME_VERSIONS to test, these correspond to language versions
      args: additional arguments to be passed to the test binary beyond ones corresponding to the arguments to this function
      deps: additional test dependencies beyond the acceptance package
      argsmap: version specific arguments map where the key is the version and the value is a list of flags that will be passed to the acceptance test framework
      **kwargs: this argument captures all additional arguments and forwards them to the generated go_test rule
    """

    test_args = _build_args(args, name, testdata, builder, structure_test_config)
    data = _build_data(structure_test_config, builder, testdata)
    deps = _build_deps(deps)

    _build_tests(name, srcs, test_args, data, deps, versions, argsmap, **kwargs)
    _cloudbuild_targets(name, srcs, structure_test_config, builder, args, deps, versions, argsmap, testdata)

def _build_tests(name, srcs, args, data, deps, versions, argsmap, **kwargs):
    # if there are no versions passed in then create a go_test(...) rule directly without changing
    # the name of the test
    if versions == None:
        _new_go_test_for_single_version(name, srcs, args, data, deps, **kwargs)
    else:
        _new_go_test_for_versions(versions, name, srcs, args, data, deps, argsmap, **kwargs)

def _new_go_test_for_versions(versions, name, srcs, args, data, deps, argsmap, **kwargs):
    tests = []
    for v in versions:
        ver_name = v + "_" + name
        tests.append(ver_name)
        ver_args = list(args)
        ver_args.append("-runtime-version=" + v)

        if argsmap != None and argsmap.get(v) != None:
            for ak, av in argsmap[v].items():
                ver_args.append(ak + "=" + av)

        _new_go_test(ver_name, srcs, ver_args, data, deps, **kwargs)

    # Each of the go_test(...) rules generated by _new_go_test have a name that is hard for
    # developers to reference, for example, '//acceptance:gcp_test_3.1.416'. To make this
    # easy to use, generate a test suite with the name of the original build target, so
    # developers can reference simply '//acceptance:gcp_test'.
    native.test_suite(
        name = name,
        tests = tests,
    )
    _new_bin_filegroup_alias(name, tests[0])

def _new_go_test_for_single_version(name, srcs, args, data, deps, **kwargs):
    _new_go_test(name, srcs, args, data, deps, **kwargs)
    _new_bin_filegroup_alias(name, name)

def _new_go_test(name, srcs, args, data, deps, **kwargs):
    go_test(
        name = name,
        size = "enormous",
        srcs = srcs,
        args = args,
        data = data,
        tags = [
            "local",
        ],
        gc_linkopts = [],
        deps = deps,
        **kwargs
    )

def _new_bin_filegroup_alias(name, test_name):
    # The test binaries generated in this file have names such as '1.13_gae_test'. For external
    # consumers who wish to invoke the test binary directly, the following filegroup gives them
    # a static name that they can reference such as 'gae_test_bin'.
    native.filegroup(
        name = name + "_bin",
        srcs = [":" + test_name],
        testonly = 1,
    )

def _build_args(args, name, testdata, builder, structure_test_config):
    short_name = _remove_suffix(name, "_test")
    builder_name = _extract_builder_name(builder)

    if args == None:
        args = []
    else:
        # make a copy of the args list to prevent mutating the passed in value
        args = list(args)
    args.append("-test-data=$(location " + testdata + ")")
    args.append("-structure-test-config=$(location " + structure_test_config + ")")
    args.append("-builder-source=$(location " + builder + ")")
    args.append("-builder-prefix=" + builder_name + "-" + short_name + "-acceptance-test-")
    args.append("-runtime-name=" + builder_name)
    return args

def _build_data(structure_test_config, builder, testdata):
    return [
        structure_test_config,
        builder,
        testdata,
    ]

def _extract_builder_name(builder):
    # A builder target is a full google3 path, the name of the builder, and then :builder.tar, the following
    # extracts the name of the builder.
    builder_name = _remove_suffix(builder, ":builder.tar")
    builder_name = builder_name[builder_name.rindex("/") + 1:]
    return builder_name

def _build_deps(deps):
    if deps == None:
        deps = []
    else:
        # make a copy of the list to prevent mutating a shared 'deps' value declared in a BUILD file
        deps = list(deps)
    deps.append("//internal/acceptance")
    return deps

# Once bazel supports python 3.9, this function can be replaced with `value.removesuffix(suffix)`:
def _remove_suffix(value, suffix):
    if value.endswith(suffix):
        value = value[:-len(suffix)]
    return value

# _cloudbuild_targets builds two rules that can be used to run the acceptance tests in gcloud.
def _cloudbuild_targets(name, srcs, structure_test_config, builder, args, deps, versions, argsmap, testdata):
    bin_name = _build_cloudbuild_test_binary(name, srcs, deps)

    # this hack is to prevent the tarball from being built in bazel, as _build_testdata_target
    # makes use of Fileset which is not available in bazel.
    if testdata.startswith("//third_party/gcp_buildpacks"):
        _build_cloudbuild_zip(name, bin_name, structure_test_config, builder, testdata)
    _build_cloudbuild_config_target(name, bin_name, builder, args, versions, argsmap, testdata)

def _build_cloudbuild_zip(name, bin_name, structure_test_config, builder, testdata):
    testdata_fileset_name = _build_testdata_target(name, testdata)
    pkg_zip(
        name = name + "_cloudbuild",
        srcs = [
            bin_name,
            builder,
            testdata_fileset_name,
            structure_test_config,
        ],
        testonly = 1,
    )

def _build_cloudbuild_test_binary(name, srcs, deps):
    bin_name = name + "_cloudbuild_bin"
    _new_go_test(bin_name, srcs, None, None, deps)
    return bin_name

# _build_testdata_target creates a Fileset target for the given testdata label. The reason to do
# this is our testdata is accessed via exports_files(...) which copies the testdata into writeable
# folders. The acceptance test suite relies on the the folders being writeable. However, most Bazel
# rules, including pkg_zip or pkg_tar, are not able to accept source files that are directories. It
# requires an explicit fileset to bring in a directory of files.
def _build_testdata_target(name, testdata):
    testdata_label = testdata[testdata.rfind(":") + 1:]
    fileset_name = name + "_" + testdata_label
    native.Fileset(
        name = fileset_name,
        out = name + "_generated/" + testdata_label,
        entries = [
            native.FilesetEntry(
                srcdir = testdata,
            ),
        ],
    )
    return fileset_name

def _build_cloudbuild_config_target(name, bin_name, builder, args, versions, argsmap, testdata):
    cloudbuild_config = _build_cloudbuild_config(name, bin_name, builder, args, versions, argsmap, testdata)
    native.genrule(
        name = name + "_cloudbuild_config",
        outs = [name + "_cloudbuild.yaml"],
{'fraction_non_alphanumeric': 0.08899247801673905, 'fraction_numerical': 0.004873397605678568, 'mean_word_length': 3.3063412408759123, 'pattern_counts': {'":': 8, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3716922', 'n_tokens_mistral': 5844, 'n_tokens_neox': 5723, 'n_words': 1865}