From be8e3b402550330d1ea994928536be9e4688d4b0 Mon Sep 17 00:00:00 2001 From: Chuck Grindel Date: Mon, 15 Nov 2021 14:54:06 -0700 Subject: [PATCH] Added documentation to the `README.md` (#3) Added documentation to the README.md Added an additional Bazel version for demonstration purposes. Added find_child_workspace_packages.sh and common.sh. Refactored update_deleted_packages.sh to use the other scripts. Added tests for all of these bash scripts. --- README.md | 264 +++++++++++++++++- .../internal/integration_test_runner.sh | 5 - bazel_versions.bzl | 1 + tools/BUILD.bazel | 120 +++++++- tools/assertions.sh | 16 ++ tools/common.sh | 58 ++++ tools/find_child_workspace_packages.sh | 91 ++++++ tools/find_child_workspace_packages_test.sh | 41 +++ tools/join_by_test.sh | 27 ++ tools/normalize_path_test.sh | 30 ++ tools/print_by_line_test.sh | 30 ++ tools/setup_test_workspace.sh | 57 ++++ tools/sort_items_test.sh | 27 ++ tools/update_deleted_packages.sh | 121 +++----- tools/update_deleted_packages_test.sh | 66 +++++ tools/upsearch_test.sh | 38 +++ 16 files changed, 907 insertions(+), 85 deletions(-) create mode 100644 tools/assertions.sh create mode 100644 tools/common.sh create mode 100755 tools/find_child_workspace_packages.sh create mode 100755 tools/find_child_workspace_packages_test.sh create mode 100755 tools/join_by_test.sh create mode 100755 tools/normalize_path_test.sh create mode 100755 tools/print_by_line_test.sh create mode 100644 tools/setup_test_workspace.sh create mode 100755 tools/sort_items_test.sh create mode 100755 tools/update_deleted_packages_test.sh create mode 100755 tools/upsearch_test.sh diff --git a/README.md b/README.md index 060ab0b5..cbdd5ad1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,262 @@ -# rules_bazel_integration_test -Rules and macros for executing integration tests that use Bazel. Supports running integration tests with multiple versions of Bazel. +# Bazel Integration Rules + +[![Build](https://github.com/cgrindel/rules_bazel_integration_test/actions/workflows/bazel.yml/badge.svg)](https://github.com/cgrindel/rules_bazel_integration_test/actions/workflows/bazel.yml) + +This repository contains [Bazel](https://bazel.build/) macros that execute integration tests that +use Bazel (e.g. execute tests in a child workspace). The macros support running integration tests +with multiple versions of Bazel. + +## Quickstart + +The following provides a quick introduction on how to use the rules in this repository. Also, check +out [the documentation](/doc/), the [integration test defined in this repo](/examples/BUILD.bazel), +and [the bazel_integration_test_example](https://github.com/cgrindel/bazel_integration_test_example) +for more information. + +### 1. Configure your workspace to use `rules_bazel_integration_test` + +Add the following to your `WORKSPACE` file to add this repository and its dependencies. + +```python +# TODO: Add http_archive + +load("@cgrindel_rules_bazel_integration_test//bazel_integration_test:deps.bzl", "bazel_integration_test_rules_dependencies") + +bazel_integration_test_rules_dependencies() + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() + +load("@cgrindel_rules_bzlformat//bzlformat:deps.bzl", "bzlformat_rules_dependencies") + +bzlformat_rules_dependencies() + +load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies") + +bazel_starlib_dependencies() +``` + +### 2. Create a `bazel_versions.bzl` in the root of your repository + +Add the following to a file called `bazel_versions.bzl` at the root of your repository. Replace the +Bazel version values with the values that you would like to test against for your integration tests. + +```python +CURRENT_BAZEL_VERSION = "4.2.1" + +OTHER_BAZEL_VERSIONS = [ + "5.0.0-pre.20211011.2", + "6.0.0-pre.20211101.2", +] + +SUPPORTED_BAZEL_VERSIONS = [ + CURRENT_BAZEL_VERSION, +] + OTHER_BAZEL_VERSIONS + +``` + +NOTE: The above code designates a current version and other versions. This can be useful if you have +a large number of integration tests where you want to execute all of them against the current +version and execute a subset of them against other Bazel versions. + +Add the following to the Bazel build file in the same package as the `bazel_versions.bzl` file. + +```python +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "bazel_versions", + srcs = ["bazel_versions.bzl"], + visibility = ["//:__subpackages__"], +) +``` + +### 3. Declare the Bazel binaries in the `WORKSPACE` file + +Back in the `WORKSPACE` file, add the following to download and prepare the Bazel binaries for +testing. + +```python +load("//:bazel_versions.bzl", "SUPPORTED_BAZEL_VERSIONS") +load("@build_bazel_integration_testing//tools:repositories.bzl", "bazel_binaries") + +bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS) +``` + +### 4. Configure the deleted packages for the parent workspace + +If you have child workspaces under your parent workspace, you need to tell Bazel to ignore the child +workspaces. This can be done in one of two ways: + +1. Add entries to the `.bazelignore` file in the parent workspace. +2. Specify a list of deleted packages using the `--deleted_packages` flag. + +For `glob()` in the parent workspace to find files in the child workspaces, we need to use the +deleted packages mechanism. + +Add the following to the `.bazelrc` in the parent workspace. Leave the values blank for now. + +```conf +# To update these lines, execute +# `bazel run @cgrindel_rules_bazel_integration_test//tools:update_deleted_packages` +build --deleted_packages= +query --deleted_packages= +``` + +To populate the values, we will run a utility that looks for child workspaces (i.e., `WORKSPACE` +files) and find all of the directories that have Bazel build files (i.e., `BUILD`, `BUILD.bazel`). +Execute the following in a parent workspace directory. + +```sh +# Populate the --deleted_packages flags in the .bazelrc +$ bazel run @cgrindel_rules_bazel_integration_test//tools:update_deleted_packages +``` + +After running the utility, the `--deleted_packages` entries in the `.bazelrc` should have a +comma-separated list of packages under the child workspaces. + +### 5. Define integration test targets + +For the purposes of this example, lets assume that we have an `examples` directory which contains a +subdirectory called `simple`. The `simple` directory contains another Bazel workspace (i.e. has its +own `WORKSPACE` file) that does not have a dependency on the parent workspace. We want to +execute tests in the `simple` workspace using different versions of Bazel. + +Add the following to the `BUILD.bazel` file in the `examples` directory. + +```python +load("//:bazel_versions.bzl", "CURRENT_BAZEL_VERSION", "OTHER_BAZEL_VERSIONS") +load( + "@cgrindel_rules_bazel_integration_test//bazel_integration_test:bazel_integration_test.bzl", + "bazel_integration_test", + "bazel_integration_tests", + "integration_test_utils", +) + +# If you only want to execute against a single version of Bazel, use +# bazel_integration_test. +bazel_integration_test( + name = "simple_test", + bazel_version = CURRENT_BAZEL_VERSION, +) + +# If you want to execute an integration test using multiple versions of Bazel, +# use bazel_integration_tests. It will generate multiple integration test +# targets with names derived from the base name and the bazel version. +bazel_integration_tests( + name = "simple_test", + bazel_versions = OTHER_BAZEL_VERSIONS, +) + +# By default, the integration test targets are tagged as `manual`. This +# prevents the targets from being found from most target expansion (e.g. +# `//...`, `:all`). To easily execute a group of integration tests, you may +# want to define a test suite which includes the desired test targets. +test_suite( + name = "all_integration_tests", + # If you don't apply the test tags to the test suite, the test suite will + # be found when `bazel test //...` is executed. + tags = integration_test_utils.DEFAULT_INTEGRATION_TEST_TAGS, + tests = [":simple_test"] + + integration_test_utils.bazel_integration_test_names( + "simple_test", + OTHER_BAZEL_VERSIONS, + ), + visibility = ["//:__subpackages__"], +) +``` + +The above code defines three test targets: `//examples:simple_test`, +`//examples:simple_test_bazel_5_0_0-pre_20211011_2`, and +`//examples:simple_test_bazel_6_0_0-pre_20211101_2`. The `all_integration_tests` target is a test +suite that executes all of the integration tests with a single command: + +```sh +# Execute all of the integration tests +$ bazel test //examples:all_integration_tests +``` + +## Integration Tests That Depend Upon The Parent Workspace + +In the quickstart example, the child workspace does not reference the parent workspace. In many +cases, a child workspace will reference the parent workspace using a +[local_repository](https://docs.bazel.build/versions/main/be/workspace.html#local_repository) +declaration to demonstrate functionality from the parent workspace. + +```python +# Child WORKSPACE at examples/simple/WORKSPACE + +# Reference the parent workspace +local_repository( + name = "my_parent_workspace", + path = "../..", +) +``` + +This section explains how to use `rules_bazel_integration_test` in this situation. To review working +examples, check out [rules_updatesrc](https://github.com/cgrindel/rules_updatesrc), +[rules_bzlformat](https://github.com/cgrindel/rules_bzlformat), and +[rules_spm](https://github.com/cgrindel/rules_spm). + +### 1. Declare a `filegroup` to represent the parent workspace files + +The child workspace needs to access parent workspace files. To easily reference the files, declare a +`filegroup` at the root of the parent workspace to collect all of the files that the child workspace +needs. The values listed in the `srcs` should include every file and/or package that the child +workspaces require. + +```python +# This target collects all of the parent workspace files needed by the child workspaces. +filegroup( + name = "local_repository_files", + # Include every package that is required by the child workspaces. + srcs = [ + "BUILD.bazel", + "WORKSPACE", + "//spm:all_files", + "//spm/internal:all_files", + "//spm/internal/modulemap_parser:all_files", + ], + visibility = ["//:__subpackages__"], +) +``` + +In every parent workspace package that is listed in the `srcs` above, create a filegroup globbing +all of the files in the package. + +```python +# Add to every package that is required by a child workspace. +filegroup( + name = "all_files", + srcs = glob(["*"]), + visibility = ["//:__subpackages__"], +) +``` + +### 2. Update the integration test targets to include the parent workspace files + +The `bazel_integration_test` and `bazel_integration_tests` declarations include a `workspace_files` +attribute. If not specified, it defaults to a custom glob expression selecting files under the +child workspace directory. To include the parent workspace files, add the attribute with an +expression that globs the workspace files and the `//:local_repository_files` target. + +```python +bazel_integration_test( + name = "simple_test", + bazel_version = CURRENT_BAZEL_VERSION, + workspace_files = integration_test_utils.glob_workspace_files("simple") + [ + "//:local_repository_files", + ], +) +``` + +### 3. Execute the integration tests + +Execute the integration test. + +```sh +# Execute the integration test called simple_test +$ bazel test //examples:simple_test +``` + diff --git a/bazel_integration_test/internal/integration_test_runner.sh b/bazel_integration_test/internal/integration_test_runner.sh index db8994a9..447c0df6 100755 --- a/bazel_integration_test/internal/integration_test_runner.sh +++ b/bazel_integration_test/internal/integration_test_runner.sh @@ -9,11 +9,6 @@ exit_on_error() { exit 1 } -normalize_path() { - local path="${1}" - echo "$(cd "${path}" > /dev/null && pwd)" -} - bazel_cmds=() # Process args diff --git a/bazel_versions.bzl b/bazel_versions.bzl index 328470f8..b2e59b6a 100644 --- a/bazel_versions.bzl +++ b/bazel_versions.bzl @@ -2,6 +2,7 @@ CURRENT_BAZEL_VERSION = "4.2.1" OTHER_BAZEL_VERSIONS = [ "5.0.0-pre.20211011.2", + "6.0.0-pre.20211101.2", ] SUPPORTED_BAZEL_VERSIONS = [ diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 05392015..3f3ba4fe 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -2,4 +2,122 @@ load("@cgrindel_rules_bzlformat//bzlformat:bzlformat.bzl", "bzlformat_pkg") bzlformat_pkg(name = "bzlformat") -exports_files(["update_deleted_packages.sh"]) +# MARK: - Binary Declarations + +sh_library( + name = "common", + srcs = ["common.sh"], +) + +sh_binary( + name = "find_child_workspace_packages", + srcs = ["find_child_workspace_packages.sh"], + visibility = ["//visibility:public"], + deps = [ + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_binary( + name = "update_deleted_packages", + srcs = ["update_deleted_packages.sh"], + data = [ + ":find_child_workspace_packages", + ], + visibility = ["//visibility:public"], + deps = [ + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +# MARK: - Test Declarations + +sh_library( + name = "assertions", + testonly = 1, + srcs = ["assertions.sh"], +) + +sh_test( + name = "normalize_path_test", + srcs = ["normalize_path_test.sh"], + deps = [ + ":assertions", + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "upsearch_test", + srcs = ["upsearch_test.sh"], + deps = [ + ":assertions", + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "sort_items_test", + srcs = ["sort_items_test.sh"], + deps = [ + ":assertions", + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "print_by_line_test", + srcs = ["print_by_line_test.sh"], + deps = [ + ":assertions", + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "join_by_test", + srcs = ["join_by_test.sh"], + deps = [ + ":assertions", + ":common", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_library( + name = "setup_test_workspace", + testonly = 1, + srcs = ["setup_test_workspace.sh"], +) + +sh_test( + name = "find_child_workspace_packages_test", + srcs = ["find_child_workspace_packages_test.sh"], + data = [ + ":find_child_workspace_packages", + ], + deps = [ + ":assertions", + ":setup_test_workspace", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "update_deleted_packages_test", + srcs = ["update_deleted_packages_test.sh"], + data = [ + ":update_deleted_packages", + ], + deps = [ + ":assertions", + ":setup_test_workspace", + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/tools/assertions.sh b/tools/assertions.sh new file mode 100644 index 00000000..7cb22aa3 --- /dev/null +++ b/tools/assertions.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +fail() { + local err_msg="${1:-}" + [[ -n "${err_msg}" ]] || err_msg="Unspecified error occurred." + echo >&2 "${err_msg}" + exit 1 +} + +assert_equal() { + local expected="${1}" + local actual="${2}" + local err_msg="${3:-Expected to be equal. expected: ${expected}, actual: ${actual}}" + [[ "${expected}" == "${actual}" ]] || fail "${err_msg}" +} + diff --git a/tools/common.sh b/tools/common.sh new file mode 100644 index 00000000..77addd59 --- /dev/null +++ b/tools/common.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +exit_on_error() { + local err_msg="${1:-}" + [[ -n "${err_msg}" ]] || err_msg="Unspecified error occurred." + echo >&2 "${err_msg}" + exit 1 +} + +normalize_path() { + local path="${1}" + if [[ -d "${path}" ]]; then + local dirname="${path}" + else + local dirname="$(dirname "${path}")" + local basename="$(basename "${path}")" + fi + dirname="$(cd "${dirname}" > /dev/null && pwd)" + if [[ -z "${basename:-}" ]]; then + echo "${dirname}" + else + echo "${dirname}/${basename}" + fi +} + +# Lovingly inspired by https://unix.stackexchange.com/a/13474. +upsearch() { + local target_file="${1}" + slashes=${PWD//[^\/]/} + directory="$PWD" + for (( n=${#slashes}; n>0; --n )) + do + local test_path="${directory}/${target_file}" + test -e "${test_path}" && \ + normalize_path "${test_path}" && \ + return + directory="${directory}/.." + done +} + +sort_items() { + local IFS=$'\n' + sort -u <<<"$*" +} + +print_by_line() { + for item in "${@:-}" ; do + echo "${item}" + done +} + +# Lovingly inspired by https://dev.to/meleu/how-to-join-array-elements-in-a-bash-script-303a +join_by() { + local IFS="$1" + shift + echo "$*" +} + diff --git a/tools/find_child_workspace_packages.sh b/tools/find_child_workspace_packages.sh new file mode 100755 index 00000000..26b4a194 --- /dev/null +++ b/tools/find_child_workspace_packages.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +# This was lovingly inspired by +# https://github.com/bazelbuild/rules_python/blob/main/tools/bazel_integration_test/update_deleted_packages.sh. + +# This utility will find all of the child workspace directories (i.e., contains +# WORKSPACE file) in a Bazel workspace. It is used in conjunction with +# update_deleted_packages.sh. + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + # Do not fail if this logic does not succeed. We are supporting being run + # outside of Bazel. + # { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e + f=; set -e +# --- end runfiles.bash initialization v2 --- + +# Do not use helper functions as they have not been loaded yet +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)" + +# If we are being run via `bazel run` the rlocation function will exist and we +# will load the common functions using rlocation. Otherwise, we are being executed +# directly. +if [[ $(type -t rlocation) == function ]]; then + common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +else + common_lib="${script_dir}/common.sh" +fi +source "${common_lib}" + +# MARK - Functions + +find_workspace_dirs() { + local path="${1}" + find "${path}" -name "WORKSPACE" | xargs -n 1 dirname +} + +find_bazel_pkgs() { + local path="${1}" + find "${path}" \( -name BUILD -or -name BUILD.bazel \) | xargs -n 1 dirname +} + +# MARK - Main + +starting_dir=$(pwd) + +# Make sure that we end up back in the original directory. +cleanup() { + cd "${starting_dir}" +} +trap cleanup EXIT + +# Process args +while (("$#")); do + case "${1}" in + "--workspace") + workspace_root="${2}" + shift 2 + ;; + *) + shift 1 ;; + esac +done + +[[ -z "${workspace_root:-}" ]] && [[ ! -z "${BUILD_WORKING_DIRECTORY:-}" ]] && workspace_root="${BUILD_WORKING_DIRECTORY:-}" +[[ -z "${workspace_root:-}" ]] && workspace_root="$(dirname "$(upsearch WORKSPACE)")" +[[ -d "${workspace_root:-}" ]] || exit_on_error "The workspace root was not found. ${workspace_root:-}" + +all_workspace_dirs=( $(find_workspace_dirs "${workspace_root}") ) +child_workspace_dirs=() +for workspace_dir in "${all_workspace_dirs[@]}" ; do + [[ "${workspace_dir}" != "${workspace_root}" ]] && \ + child_workspace_dirs+=( "${workspace_dir}" ) +done + +absolute_path_pkgs=() +for child_workspace_dir in "${child_workspace_dirs[@]}" ; do + absolute_path_pkgs+=( $(find_bazel_pkgs "${child_workspace_dir}") ) +done +absolute_path_pkgs=( $(sort_items "${absolute_path_pkgs[@]}") ) + +# Strip the workspace_root prefix from the paths +pkgs=( "${absolute_path_pkgs[@]#"${workspace_root}/"}") + +print_by_line "${pkgs[@]}" diff --git a/tools/find_child_workspace_packages_test.sh b/tools/find_child_workspace_packages_test.sh new file mode 100755 index 00000000..ed7c2a70 --- /dev/null +++ b/tools/find_child_workspace_packages_test.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +find_bin="$(rlocation cgrindel_rules_bazel_integration_test/tools/find_child_workspace_packages.sh)" + +starting_path="${PWD}" + +# Set up the parent workspace +setup_workspace_script="$(rlocation cgrindel_rules_bazel_integration_test/tools/setup_test_workspace.sh)" +source "${setup_workspace_script}" + +expected=("examples/child_a" "examples/child_a/foo" "somewhere_else/child_b/bar") + +# Execute specifying workspace flag +actual=( $(. "${find_bin}" --workspace "${parent_dir}") ) +assert_equal "${#expected[@]}" "${#actual[@]}" +for (( i = 0; i < ${#expected[@]}; i++ )); do + assert_equal "${expected[i]}" "${actual[i]}" +done + + +# Execute inside the parent workspace; find the parent workspace root +cd "${examples_dir}" +actual=( $(. "${find_bin}") ) +assert_equal "${#expected[@]}" "${#actual[@]}" +for (( i = 0; i < ${#expected[@]}; i++ )); do + assert_equal "${expected[i]}" "${actual[i]}" +done diff --git a/tools/join_by_test.sh b/tools/join_by_test.sh new file mode 100755 index 00000000..daf083e6 --- /dev/null +++ b/tools/join_by_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +source "${common_lib}" + + +args=(a c b) + +actual="$(join_by , "${args[@]}")" +assert_equal "a,c,b" "${actual}" + +actual="$(join_by "|" "${args[@]}")" +assert_equal "a|c|b" "${actual}" diff --git a/tools/normalize_path_test.sh b/tools/normalize_path_test.sh new file mode 100755 index 00000000..bbfb25fc --- /dev/null +++ b/tools/normalize_path_test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +source "${common_lib}" + +target_dir="foo" +target_file="${target_dir}/bar" + +mkdir -p "${target_dir}" +touch "${target_file}" + +normalized_dir="$(normalize_path "${target_dir}")" +assert_equal "${PWD}/${target_dir}" "${normalized_dir}" + +normalized_file="$(normalize_path "${target_file}")" +assert_equal "${PWD}/${target_file}" "${normalized_file}" diff --git a/tools/print_by_line_test.sh b/tools/print_by_line_test.sh new file mode 100755 index 00000000..b3984dd0 --- /dev/null +++ b/tools/print_by_line_test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +source "${common_lib}" + +# No args +actual=$(print_by_line) +assert_equal "" "${actual}" + +# With args +array=(first second third) +actual=$(print_by_line "${array[@]}") +expected="first +second +third" +assert_equal "${expected}" "${actual}" diff --git a/tools/setup_test_workspace.sh b/tools/setup_test_workspace.sh new file mode 100644 index 00000000..83acbd2d --- /dev/null +++ b/tools/setup_test_workspace.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# This script is sourced by tests that need a parent workspace with child workspaces. + +parent_dir="${PWD}/parent" +examples_dir="${parent_dir}/examples" + +child_a_dir="${examples_dir}/child_a" +child_a_pkg_dir="${child_a_dir}/foo" + +child_b_dir="${parent_dir}/somewhere_else/child_b" +child_b_pkg_dir="${child_b_dir}/bar" + +directories=("${parent_dir}" "${examples_dir}" "${child_a_dir}" "${child_b_dir}") +directories+=("${child_a_pkg_dir}" "${child_b_pkg_dir}") +for dir in "${directories[@]}" ; do + mkdir -p "${dir}" +done + +parent_workspace="${parent_dir}/WORKSPACE" +child_a_workspace="${child_a_dir}/WORKSPACE" +child_b_workspace="${child_b_dir}/WORKSPACE" +workspaces=("${parent_workspace}" "${child_a_workspace}" "${child_b_workspace}") +for workspace in "${workspaces[@]}" ; do + touch "${workspace}" +done + +parent_build="${parent_dir}/BUILD.bazel" +examples_build="${examples_dir}/BUILD.bazel" +child_a_build="${child_a_dir}/BUILD" +child_a_pkg_build="${child_a_pkg_dir}/BUILD" +child_b_pkg_build="${child_b_pkg_dir}/BUILD.bazel" +build_files=("${parent_build}" "${examples_build}" "${child_a_build}" "${child_a_pkg_build}") +build_files+=("${child_b_pkg_build}") +for build_file in "${build_files[@]}" ; do + touch "${build_file}" +done + +parent_bazelrc="${parent_dir}/.bazelrc" +child_a_bazelrc="${child_a_dir}/.bazelrc" +child_b_bazelrc="${child_b_dir}/.bazelrc" +child_bazelrcs=("${child_a_bazelrc}" "${child_b_bazelrc}") +bazelrcs=("${parent_bazelrc}" "${child_a_bazelrc}" "${child_b_bazelrc}") +# Added BOF and EOF comments to make it easier to see the beginning and end of files when debugging +# failed assertions. +bazelrc_template=" +# BOF +build --deleted_packages= +query --deleted_packages= +# EOF" + +reset_bazelrc_files() { + for bazelrc in "${bazelrcs[@]}" ; do + echo "${bazelrc_template}" > "${bazelrc}" + done +} +reset_bazelrc_files diff --git a/tools/sort_items_test.sh b/tools/sort_items_test.sh new file mode 100755 index 00000000..0623868d --- /dev/null +++ b/tools/sort_items_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +source "${common_lib}" + +array=(b e a c e) +expected=(a b c e) +actual=( $(sort_items "${array[@]}") ) + +assert_equal "${#expected[@]}" "${#actual[@]}" +for (( i = 0; i < ${#expected[@]}; i++ )); do + assert_equal "${expected[i]}" "${actual[i]}" +done diff --git a/tools/update_deleted_packages.sh b/tools/update_deleted_packages.sh index f25445a4..11d90292 100755 --- a/tools/update_deleted_packages.sh +++ b/tools/update_deleted_packages.sh @@ -3,66 +3,45 @@ # This was lovingly inspired by # https://github.com/bazelbuild/rules_python/blob/main/tools/bazel_integration_test/update_deleted_packages.sh. -# For integration tests, we want to be able to glob() up the sources inside a nested package -# See explanation in .bazelrc - -set -euo pipefail - -# MARK - Functions - -exit_on_error() { - local err_msg="${1:-}" - [[ -n "${err_msg}" ]] || err_msg="Unspecified error occurred." - echo >&2 "${err_msg}" - exit 1 -} - -normalize_path() { - local path="${1}" - if [[ -d "${path}" ]]; then - local dirname="${path}" - else - local dirname="$(dirname "${path}")" - local basename="$(basename "${path}")" - fi - dirname="$(cd "${dirname}" > /dev/null && pwd)" - if [[ -z "${basename:-}" ]]; then - echo "${dirname}" - else - echo "${dirname}/${basename}" - fi -} - -# Lovingly inspired by https://unix.stackexchange.com/a/13474. -upsearch() { - local target_file="${1}" - slashes=${PWD//[^\/]/} - directory="$PWD" - for (( n=${#slashes}; n>0; --n )) - do - local test_path="${directory}/${target_file}" - test -e "${test_path}" && echo "${test_path}" && return - directory="${directory}/.." - done -} - -find_bazel_pkgs() { - local path="${1}" - find "${path}" \( -name BUILD -or -name BUILD.bazel \) | xargs -n 1 dirname -} - -# Lovingly inspired by https://dev.to/meleu/how-to-join-array-elements-in-a-bash-script-303a -join_by() { - local IFS="$1" - shift - echo "$*" -} - -sort_items() { - local IFS=$'\n' - shift - sort -u <<<"$*" -} +# For integration tests, we want to be able to glob() up the sources for child workspaces. To do +# this and not have the parent workspace "see" the child workspaces, we specify the packages for the +# child worksapces as deleted packages in the parent workspace using the --deleted_packages flag. +# +# The .bazelrc for the parent workspace must contain the following lines: +# build --deleted_packages= +# query --deleted_packages= +# This utility will find the child workspaces and identify all of the Bazel packages under the child +# workspaces. It will then update the value for the --deleted_packages lines in the parent .bazelrc +# with a comma-separated list of the child workspace packages. + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + # Do not fail if this logic does not succeed. We are supporting being run + # outside of Bazel. + # { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e + f=; set -e +# --- end runfiles.bash initialization v2 --- + +# Do not use helper functions as they have not been loaded yet +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)" + +# If we are being run via `bazel run` the rlocation function will exist and we +# will load the common functions using rlocation. Otherwise, we are being executed +# directly. +if [[ $(type -t rlocation) == function ]]; then + common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" + find_pkgs_script="$(rlocation cgrindel_rules_bazel_integration_test/tools/find_child_workspace_packages.sh)" +else + common_lib="${script_dir}/common.sh" + find_pkgs_script="${script_dir}/find_child_workspace_packages.sh" +fi +source "${common_lib}" # MARK - Main @@ -93,27 +72,15 @@ while (("$#")); do esac done -[[ -z "${bazelrc_path:-}" ]] && bazelrc_path=$(upsearch .bazelrc) -[[ -f "${bazelrc_path:-}" ]] || exit_on_error "The bazelrc was not found. ${bazelrc_path:-}" - +[[ -z "${workspace_root:-}" ]] && [[ ! -z "${BUILD_WORKING_DIRECTORY:-}" ]] && workspace_root="${BUILD_WORKING_DIRECTORY:-}" [[ -z "${workspace_root:-}" ]] && workspace_root="$(dirname "$(upsearch WORKSPACE)")" [[ -d "${workspace_root:-}" ]] || exit_on_error "The workspace root was not found. ${workspace_root:-}" -[[ ${#pkg_search_dirs[@]} == 0 ]] && \ - examples_dir="${workspace_root}/examples" && \ - [[ -d "${examples_dir}" ]] && \ - pkg_search_dirs+=( $(find ${examples_dir}/* -maxdepth 1 -type directory) ) - -[[ ${#pkg_search_dirs[@]} -gt 0 ]] || exit_on_error "No search directories were specified." - -absolute_path_pkgs=() -for search_dir in "${pkg_search_dirs[@]}" ; do - absolute_path_pkgs+=( $(find_bazel_pkgs "${search_dir}") ) -done -absolute_path_pkgs=( $(sort_items "${absolute_path_pkgs[@]}") ) +[[ -z "${bazelrc_path:-}" ]] && bazelrc_path="${workspace_root}/.bazelrc" +[[ -f "${bazelrc_path:-}" ]] || exit_on_error "The bazelrc was not found. ${bazelrc_path:-}" -# Strip the workspace_root prefix from the paths -pkgs=( "${absolute_path_pkgs[@]#"${workspace_root}/"}") +# Find the child packages +pkgs=( $(. "${find_pkgs_script}" --workspace "${workspace_root}") ) # Update the .bazelrc file with the deleted packages flag. # The sed -i.bak pattern is compatible between macos and linux diff --git a/tools/update_deleted_packages_test.sh b/tools/update_deleted_packages_test.sh new file mode 100755 index 00000000..ef7d96ca --- /dev/null +++ b/tools/update_deleted_packages_test.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +update_bin="$(rlocation cgrindel_rules_bazel_integration_test/tools/update_deleted_packages.sh)" + +starting_path="${PWD}" + +# Set up the parent workspace +setup_workspace_script="$(rlocation cgrindel_rules_bazel_integration_test/tools/setup_test_workspace.sh)" +source "${setup_workspace_script}" + +# MARK - Test Specifying Flags + +# Execute specifying workspace flag +. "${update_bin}" --workspace "${parent_dir}" --bazelrc "${parent_bazelrc}" + +expected_with_change=" +# BOF +build --deleted_packages=examples/child_a,examples/child_a/foo,somewhere_else/child_b/bar +query --deleted_packages=examples/child_a,examples/child_a/foo,somewhere_else/child_b/bar +# EOF" + +actual=$(< "${parent_bazelrc}") +assert_equal "${expected_with_change}" "${actual}" + +for child_bazelrc in "${child_bazelrcs[@]}" ; do + actual=$(< "${child_bazelrc}") + assert_equal "${bazelrc_template}" "${actual}" +done + +# MARK - Rest Bazelrcs + +reset_bazelrc_files + +# MARK - Test Specifying Flags + +# Execute inside the parent workspace; find the parent workspace root +cd "${examples_dir}" +. "${update_bin}" + +expected_with_change=" +# BOF +build --deleted_packages=examples/child_a,examples/child_a/foo,somewhere_else/child_b/bar +query --deleted_packages=examples/child_a,examples/child_a/foo,somewhere_else/child_b/bar +# EOF" + +actual=$(< "${parent_bazelrc}") +assert_equal "${expected_with_change}" "${actual}" + +for child_bazelrc in "${child_bazelrcs[@]}" ; do + actual=$(< "${child_bazelrc}") + assert_equal "${bazelrc_template}" "${actual}" +done diff --git a/tools/upsearch_test.sh b/tools/upsearch_test.sh new file mode 100755 index 00000000..c051d46a --- /dev/null +++ b/tools/upsearch_test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +assertions_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/assertions.sh)" +source "${assertions_lib}" + +common_lib="$(rlocation cgrindel_rules_bazel_integration_test/tools/common.sh)" +source "${common_lib}" + +starting_dir="${PWD}" + +# Create a subdirectory +subdir="path/to/search/from" +mkdir -p "${subdir}" + +# Create a file to find +target_file="file_to_find" +touch "${target_file}" + +cd "${subdir}" + +# Find a file +actual=$(upsearch "${target_file}") +assert_equal "$starting_dir/${target_file}" "${actual}" + +# Do not find a file +actual=$(upsearch "file_does_not_exist") +assert_equal "" "${actual}"