From 12ef34d0dd590efa13b05926a74f6e678f7a8f50 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 12:32:25 +0900 Subject: [PATCH 01/14] implement `cc_build_error`, `matcher`, etc --- .bazelignore | 1 + .bazeliskrc | 2 + .bazelrc | 16 + .cache/.gitignore | 2 + .gitignore | 3 + BUILD.bazel | 0 MODULE.bazel | 16 + README.md | 17 + build_script/BUILD.bazel | 16 + build_script/check_emptiness.bash | 75 +++ build_script/conditionally_execute.bash | 103 +++ lang/BUILD.bazel | 0 lang/cc/BUILD.bazel | 0 lang/cc/build_error.bzl | 619 ++++++++++++++++++ lang/cc/defs.bzl | 13 + matcher/BUILD.bazel | 0 matcher/defs.bzl | 8 + matcher/executable/BUILD.bazel | 21 + matcher/executable/contains_basic_regex.bash | 10 + .../executable/contains_extended_regex.bash | 10 + matcher/executable/has_substr.bash | 10 + matcher/matcher.bzl | 85 +++ tests/BUILD.bazel | 0 tests/cc/BUILD.bazel | 0 tests/cc/c_compile_error/BUILD.bazel | 36 + tests/cc/c_compile_error/c_compile_error.c | 6 + tests/cc/c_link_error/BUILD.bazel | 36 + tests/cc/c_link_error/c_link_error.c | 6 + tests/cc/cpp_compile_error/BUILD.bazel | 36 + .../cpp_compile_error/cpp_compile_error.cpp | 4 + tests/cc/cpp_link_error/BUILD.bazel | 36 + tests/cc/cpp_link_error/cpp_link_error.cpp | 6 + 32 files changed, 1193 insertions(+) create mode 100644 .bazelignore create mode 100644 .bazeliskrc create mode 100644 .bazelrc create mode 100644 .cache/.gitignore create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 MODULE.bazel create mode 100644 README.md create mode 100644 build_script/BUILD.bazel create mode 100755 build_script/check_emptiness.bash create mode 100755 build_script/conditionally_execute.bash create mode 100644 lang/BUILD.bazel create mode 100644 lang/cc/BUILD.bazel create mode 100644 lang/cc/build_error.bzl create mode 100644 lang/cc/defs.bzl create mode 100644 matcher/BUILD.bazel create mode 100644 matcher/defs.bzl create mode 100644 matcher/executable/BUILD.bazel create mode 100755 matcher/executable/contains_basic_regex.bash create mode 100755 matcher/executable/contains_extended_regex.bash create mode 100755 matcher/executable/has_substr.bash create mode 100644 matcher/matcher.bzl create mode 100644 tests/BUILD.bazel create mode 100644 tests/cc/BUILD.bazel create mode 100644 tests/cc/c_compile_error/BUILD.bazel create mode 100644 tests/cc/c_compile_error/c_compile_error.c create mode 100644 tests/cc/c_link_error/BUILD.bazel create mode 100644 tests/cc/c_link_error/c_link_error.c create mode 100644 tests/cc/cpp_compile_error/BUILD.bazel create mode 100644 tests/cc/cpp_compile_error/cpp_compile_error.cpp create mode 100644 tests/cc/cpp_link_error/BUILD.bazel create mode 100644 tests/cc/cpp_link_error/cpp_link_error.cpp diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..16d3c4d --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +.cache diff --git a/.bazeliskrc b/.bazeliskrc new file mode 100644 index 0000000..65af518 --- /dev/null +++ b/.bazeliskrc @@ -0,0 +1,2 @@ +BAZELISK_HOME=.cache/bazelisk +USE_BAZEL_VERSION=7.1.1 diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..6415c3f --- /dev/null +++ b/.bazelrc @@ -0,0 +1,16 @@ +# Set the cache location to local +startup --output_user_root=.cache/bazel + +# Use bzlmod +common --enable_bzlmod + +# For less error-prone testing +build --sandbox_default_allow_network=false +build --incompatible_strict_action_env +build --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +build --incompatible_enable_cc_toolchain_resolution +test --test_verbose_timeout_warnings + +# For testing convenience +common --heap_dump_on_oom +test --test_output=errors diff --git a/.cache/.gitignore b/.cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/.cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..699707c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Bazel +/bazel-* +MODULE.bazel.lock diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..442b90b --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,16 @@ +"""Module for `rules_build_error`. +""" + +module(name = "rules_build_error", version = "0.1.0") + +bazel_dep(name = "bazel_skylib", version = "1.5.0", dev_dependency = True) + +bazel_dep(name = "toolchains_llvm", version = "1.0.0", dev_dependency = True) + +llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") +llvm.toolchain( + llvm_version = "17.0.6", +) + +use_repo(llvm, "llvm_toolchain") +register_toolchains("@llvm_toolchain//:all") diff --git a/README.md b/README.md new file mode 100644 index 0000000..081d9f5 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# `rules_build_error` + +Bazel implementations to test a build error. + +## Description + +There's a situation where a developer wants to test if particular code doesn't compile. However, when using ordinary testing rules, such as `cc_test`, `bazel test` results in an error if the test code doesn't compile. + +`rules_build_error` is the repository to address such an issue. It provides some implementations to test the compilation error for each programming language. When the code written in a particular **does** compile, `bazel build` should fail. + +## Usage + +## Language-specific implementations + +### C/C++ + +## Matcher diff --git a/build_script/BUILD.bazel b/build_script/BUILD.bazel new file mode 100644 index 0000000..4895b49 --- /dev/null +++ b/build_script/BUILD.bazel @@ -0,0 +1,16 @@ +"""Scripts commonly used when validating build errors. +""" + +package( + default_visibility = ["//lang:__subpackages__"], +) + +filegroup( + name = "conditionally_execute", + srcs = ["conditionally_execute.bash"], +) + +filegroup( + name = "check_emptiness", + srcs = ["check_emptiness.bash"], +) diff --git a/build_script/check_emptiness.bash b/build_script/check_emptiness.bash new file mode 100755 index 0000000..9210392 --- /dev/null +++ b/build_script/check_emptiness.bash @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <&2 +Check if any of the files are empty. + +Usage: $0 [OPTIONS] + +OPTIONS + -f FILE_TO_CHECK + Successfully exit if the file is empty + -h + Show usage and exit + -m MESSAGE + Error message when no files are empty + -n NEW_FILE_PATH + If specified, create a new empty file +EOS +} + +exit_if_empty_file() { + # Exit if the argument is a empty text file + # + # Args: + # $1: file path + local file_path + file_path=$1 + + if [[ ! -f "${file_path}" ]]; then + echo "ERROR: ${file_path} does not exist" >&2 + exit 1 + fi + if [[ ! -s "${file_path}" ]]; then + # File is empty: exit successfully + exit 0 + fi +} + +files_to_check=() +files_to_touch=() +error_message="ERROR: No files are empty" + +while getopts "f:hm:n:" opt; do + case "${opt}" in + f) + files_to_check+=("${OPTARG}") + ;; + h) + usage + exit 0 + ;; + m) + error_message="${OPTARG}" + ;; + n) + files_to_touch+=("${OPTARG}") + ;; + esac +done +shift $((OPTIND -1)) + +# Make sure the required files are touched before exiting +if [[ "${#files_to_touch[@]}" -gt 0 ]]; then + trap 'touch "${files_to_touch[@]}"' EXIT +fi + +for file_to_check in "${files_to_check[@]}"; do + exit_if_empty_file "${file_to_check}" +done + +# Exit with error if there's no empty file +echo "${error_message}" >&2 +exit 1 diff --git a/build_script/conditionally_execute.bash b/build_script/conditionally_execute.bash new file mode 100755 index 0000000..c0d4e5d --- /dev/null +++ b/build_script/conditionally_execute.bash @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <&2 +Wrapper to conditionally execute the command. + +With the option '-f', this script can receive arbitrary number of files. +If any of them is an empty file, this script exits successfully +without executing the commands. + +Usage: $0 [OPTIONS] COMMAND + +OPTIONS + -i + Ignore error when executing the command. + -f FILE_TO_CHECK + Successfully exit if the file is empty + -h + Show usage and exit + -e STDOUT_FILE + A file path to write stderr message + -o STDOUT_FILE + A file path to write stdout message + -m MESSAGE + Message when the command fails + -n NEW_FILE_PATH + If specified, create a new empty file before executing the command + +COMMAND + Executed if there's no empty file given with '-f'. +EOS +} + +exit_if_empty_file() { + # Exit if the argument is a empty text file + # + # Args: + # $1: file path + local file_path + file_path=$1 + + if [[ ! -f "${file_path}" ]]; then + echo "ERROR: ${file_path} does not exist" >&2 + exit 1 + fi + if [[ ! -s "${file_path}" ]]; then + # File is empty: exit successfully + exit 0 + fi +} + +files_to_check=() +files_to_touch=() +ignore_error="false" +error_message="ERROR: execution failed" + +while getopts "e:f:him:n:o:" opt; do + case "${opt}" in + e) + stderr_file="${OPTARG}" + ;; + f) + files_to_check+=("${OPTARG}") + ;; + h) + usage + exit 0 + ;; + i) + ignore_error="true" + ;; + m) + error_message="${OPTARG}" + ;; + n) + files_to_touch+=("${OPTARG}") + ;; + o) + stdout_file="${OPTARG}" + ;; + esac +done +shift $((OPTIND -1)) + +# Make sure the required files are touched before exiting +if [[ "${#files_to_touch[@]}" -gt 0 ]]; then + trap 'touch "${files_to_touch[@]}"' EXIT +fi + +for file_to_check in "${files_to_check[@]}"; do + exit_if_empty_file "${file_to_check}" +done + +if [[ "${ignore_error}" == "true" ]]; then + "$@" >"${stdout_file:-"/dev/null"}" 2>"${stderr_file:-"/dev/null"}" || true +else + if ! "$@" >"${stdout_file:-"/dev/null"}" 2>"${stderr_file:-"/dev/null"}" ; then + echo "${error_message}" + exit 1 + fi +fi diff --git a/lang/BUILD.bazel b/lang/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/lang/cc/BUILD.bazel b/lang/cc/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/lang/cc/build_error.bzl b/lang/cc/build_error.bzl new file mode 100644 index 0000000..f653b1d --- /dev/null +++ b/lang/cc/build_error.bzl @@ -0,0 +1,619 @@ +"""Implement `cc_build_error`. +""" + +load( + "@bazel_tools//tools/build_defs/cc:action_names.bzl", + "ACTION_NAMES", +) +load( + "@bazel_tools//tools/cpp:toolchain_utils.bzl", + "CPP_TOOLCHAIN_TYPE", + "find_cpp_toolchain", +) + +visibility("private") + +CcBuildErrorInfo = provider( + doc = "Build error information for C/C++", + fields = { + "compile_stderr": "File: A text file containing stderr when attempting to compile", + "compile_stdout": "File: A text file containing stdout when attempting to compile", + "link_stderr": "File: A text file containing stderr when attempting to link", + "link_stdout": "File: A text file containing stdout when attempting to link", + "markers": "list[File]: Marker files for validation actions", + }, +) + +_EXTENSIONS_C = [ + ".c", + ".C", +] + +_EXTENSIONS_CPP = [ + ".cc", + ".cpp", + ".cxx", + ".c++", +] + +def _is_c(src_file): + """Whether the source file is for C. + + Args: + src_file(File): C/C++ source file + + Returns: + bool: Whether the source file is for C. + """ + return "." + src_file.extension in _EXTENSIONS_C + +def _is_cpp(src_file): + """Whether the source file is for C++. + + Args: + src_file(File): C/C++ source file + + Returns: + bool: Whether the source file is for C++. + """ + return "." + src_file.extension in _EXTENSIONS_CPP + +def _get_compile_action_name(src_file): + """Get action name for compilation + + Args: + src_file(File): C/C++ source file + + Returns: + str: Compilation action name + """ + if _is_c(src_file): + return ACTION_NAMES.c_compile + elif _is_cpp(src_file): + return ACTION_NAMES.cpp_compile + else: + fail("Unsupported file type ({})".format(src_file.path)) + +def _try_compile(ctx): + """Try C/C++ compilation. + + Args: + ctx(ctx): The rule's context. + + Returns: + struct with the following members: + output(File): Object file if the action succeeds in compiling the C/C++ code. + Empty text file otherwise + stderr(File): Stderr while compiling + stdout(File): Stdout while compiling + """ + + compile_output = ctx.actions.declare_file(ctx.label.name + "/compile_output") + compile_stderr = ctx.actions.declare_file(ctx.label.name + "/compile_stderr") + compile_stdout = ctx.actions.declare_file(ctx.label.name + "/compile_stdout") + + cc_toolchain = find_cpp_toolchain(ctx) + features = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + ccopts = ctx.fragments.cpp.copts + ctx.attr.copts + if _is_cpp(ctx.file.src): + ccopts += ctx.fragments.cpp.cxxopts + + compile_variables = cc_common.create_compile_variables( + cc_toolchain = cc_toolchain, + feature_configuration = features, + user_compile_flags = ccopts, + ) + + compile_action_name = _get_compile_action_name(ctx.file.src) + + compiler = cc_common.get_tool_for_action( + feature_configuration = features, + action_name = compile_action_name, + ) + + compiler_options = cc_common.get_memory_inefficient_command_line( + feature_configuration = features, + action_name = compile_action_name, + variables = compile_variables, + ) + + # Input files for executing the action + inputs = [ctx.file.src] + + # Arguments for `conditionally_execute` + args = ctx.actions.args() + args.add("-i") + args.add("-e", compile_stderr) + args.add("-o", compile_stdout) + args.add("-n", compile_output) + args.add("-n", compile_stderr) + args.add("-n", compile_stdout) + + # From here on `args` is used for the compilation command + args.add(compiler) + + for dep in ctx.attr.deps: + compilation_context = dep[CcInfo].compilation_context + args.add_all(compilation_context.defines, before_each = "-D") + args.add_all(compilation_context.framework_includes, before_each = "-F") + args.add_all(compilation_context.includes, before_each = "-I") + args.add_all(compilation_context.quote_includes, before_each = "-iquote") + args.add_all(compilation_context.system_includes, before_each = "-isystem") + inputs += compilation_context.headers.to_list() + + args.add_all(compiler_options) + + args.add_all(ctx.attr.local_defines, before_each = "-D") + + args.add("-c", ctx.file.src) + args.add("-o", compile_output) + + ctx.actions.run( + outputs = [compile_output, compile_stdout, compile_stderr], + inputs = inputs, + executable = ctx.file._conditionally_execute, + arguments = [args], + tools = cc_toolchain.all_files, + ) + + return struct( + output = compile_output, + stdout = compile_stdout, + stderr = compile_stderr, + ) + +def _try_link(ctx, compile_output): + """Try linking the object file. + + Execute the linking action if the previous compilation action succeeded. + Otherwise, just create empty files. + + Args: + ctx(ctx): The rule's context. + compile_output: Output of the previous compilation action. + + Returns: + struct with the following members: + output(File): Object file if the action succeeds in linking the object file. + Empty text file otherwise + stderr(File): Stderr while linking + stdout(File): Stdout while linking + """ + + link_output = ctx.actions.declare_file(ctx.label.name + "/link_output") + link_stderr = ctx.actions.declare_file(ctx.label.name + "/link_stderr") + link_stdout = ctx.actions.declare_file(ctx.label.name + "/link_stdout") + + cc_toolchain = find_cpp_toolchain(ctx) + features = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + link_variables = cc_common.create_link_variables( + cc_toolchain = cc_toolchain, + feature_configuration = features, + user_link_flags = ctx.fragments.cpp.linkopts + ctx.attr.linkopts, + ) + + linker = cc_common.get_tool_for_action( + feature_configuration = features, + action_name = ACTION_NAMES.cpp_link_executable, + ) + + linker_options = cc_common.get_memory_inefficient_command_line( + feature_configuration = features, + action_name = ACTION_NAMES.cpp_link_executable, + variables = link_variables, + ) + + inputs = [compile_output] + ctx.attr.additional_linker_inputs + + # Arguments for `conditionally_execute` + args = ctx.actions.args() + args.add("-i") + args.add("-e", link_stderr) + args.add("-o", link_stdout) + args.add("-n", link_output) + args.add("-n", link_stderr) + args.add("-n", link_stdout) + + # If the compilation output is empty, it means the previous compilation failed. + # In such a case, linking is skipped + args.add("-f", compile_output) + + # From here on `args` is used for the linking command + args.add(linker) + args.add_all(linker_options) + for dep in ctx.attr.deps: + for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list(): + inputs += linker_input.additional_inputs + args.add_all(linker_input.user_link_flags) + for library_to_link in linker_input.libraries: + # TODO: Ideally the way of linking should be controlled by the attributes + library = library_to_link.static_library if library_to_link.static_library else library_to_link.dynamic_library + if library: + args.add("-L" + library.dirname) + args.add("-l:" + library.basename) + inputs.append(library) + + args.add("-o", link_output) + args.add(compile_output) + + ctx.actions.run( + outputs = [link_output, link_stdout, link_stderr], + inputs = inputs, + executable = ctx.file._conditionally_execute, + arguments = [args], + tools = cc_toolchain.all_files, + ) + + return struct( + output = link_output, + stdout = link_stdout, + stderr = link_stderr, + ) + +def _check_build_error(ctx, compile_output, link_output): + """Check if the C/C++ build failed. + + Force internal `ctx.actions.run` execution to fail if the C/C++ build error + did NOT occur, otherwise, create an empty text file. + + Args: + ctx(ctx): The rule's context. + compile_output: Output of the previous compilation action. + link_output: Output of the previous linking action. + + Returns: + File: An empty text file. + """ + + # Marker file for the check + marker_check_build_error = ctx.actions.declare_file( + ctx.label.name + "/marker_check_build_error", + ) + + # Arguments for `check_emptiness` + args = ctx.actions.args() + + args.add("-f", compile_output) + args.add("-f", link_output) + args.add("-m", "ERROR: C/C++ build error didn't occur") + args.add("-n", marker_check_build_error) + + ctx.actions.run( + outputs = [marker_check_build_error], + inputs = [compile_output, link_output], + executable = ctx.file._check_emptiness, + arguments = [args], + ) + + return marker_check_build_error + +def _try_build_impl(ctx): + """Implementation of the rule `try_cc_build` + + Args: + ctx(ctx): The rule's context. + + Returns: + A list of providers. + """ + compile_result = _try_compile(ctx) + link_result = _try_link(ctx, compile_result.output) + marker_check_build_error = _check_build_error( + ctx, + compile_result.output, + link_result.output, + ) + + markers = [marker_check_build_error] + return [ + CcBuildErrorInfo( + compile_stderr = compile_result.stderr, + compile_stdout = compile_result.stdout, + link_stderr = link_result.stderr, + link_stdout = link_result.stdout, + markers = markers, + ), + DefaultInfo( + # Explicitly specify the markers to make sure the checking action is evaluated + files = depset(markers), + ), + ] + +_try_build = rule( + implementation = _try_build_impl, + attrs = { + "additional_linker_inputs": attr.label_list( + doc = "Pass these files to the linker command", + allow_empty = True, + mandatory = False, + ), + "copts": attr.string_list( + doc = "Add these options to the compilation command", + allow_empty = True, + mandatory = False, + ), + "deps": attr.label_list( + doc = "The list of CcInfo libraries to be linked in to the binary target.", + allow_empty = True, + mandatory = False, + providers = [CcInfo], + ), + "linkopts": attr.string_list( + doc = "Add these options to the linker command", + allow_empty = True, + mandatory = False, + ), + "local_defines": attr.string_list( + doc = "List of pre-processor macro definitions to add to the compilation command", + allow_empty = True, + mandatory = False, + ), + "src": attr.label( + doc = "C/C++ file to be processed", + mandatory = True, + allow_single_file = _EXTENSIONS_C + _EXTENSIONS_CPP, + ), + "_cc_toolchain": attr.label( + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), + ), + "_check_emptiness": attr.label( + default = Label("//build_script:check_emptiness"), + allow_single_file = True, + cfg = "exec", + executable = True, + ), + "_conditionally_execute": attr.label( + default = Label("//build_script:conditionally_execute"), + allow_single_file = True, + cfg = "exec", + executable = True, + ), + }, + fragments = ["cpp"], + toolchains = [CPP_TOOLCHAIN_TYPE], + provides = [CcBuildErrorInfo, DefaultInfo], +) + +def _check_each_message(ctx, message_file, matcher, pattern): + """Check each message with a matcher and a pattern string. + + Args: + ctx(ctx): The rule's context. + message_file(File): A text file containing message. + matcher(File): A matcher executable. + pattern(str): A pattern string. + + Returns: + File: Marker file for the check. + """ + marker_file = ctx.actions.declare_file( + ctx.label.name + + "/marker_check_message__" + + message_file.basename + "__" + + (matcher.path if matcher else "NONE") + "__" + + (pattern if pattern else "NONE") + "__", + ) + + if not matcher: + if pattern: + fail( + "When not specifying the matcher, " + + "pattern string must be empty", + ) + + ctx.actions.run( + outputs = [marker_file], + executable = "touch", + arguments = [ + marker_file.path, + ], + ) + else: + if not pattern: + fail( + "When specifying the matcher, " + + "pattern string must not be empty", + ) + + ctx.actions.run( + outputs = [marker_file], + inputs = [message_file], + executable = ctx.file._conditionally_execute, + arguments = [ + "-m", + "Pattern '{pattern}' is not found in '{message_file}' with the matcher '{matcher}'".format( + pattern = pattern, + message_file = message_file.path, + matcher = matcher.path, + ), + "-n", + marker_file.path, + # From here, the matcher command starts + matcher.path, + pattern, + message_file.path, + ], + tools = [matcher], + ) + + return marker_file + +def _check_messages_impl(ctx): + """Implementation of `_check_messages`. + + Args: + ctx(ctx): The rule's context. + + Returns: + A list of providers. + """ + + cc_build_error_info = ctx.attr.build_trial[CcBuildErrorInfo] + marker_compile_stderr = _check_each_message( + ctx, + cc_build_error_info.compile_stderr, + ctx.file.matcher_compile_stderr, + ctx.attr.pattern_compile_stderr, + ) + marker_compile_stdout = _check_each_message( + ctx, + cc_build_error_info.compile_stdout, + ctx.file.matcher_compile_stdout, + ctx.attr.pattern_compile_stdout, + ) + marker_link_stderr = _check_each_message( + ctx, + cc_build_error_info.link_stderr, + ctx.file.matcher_link_stderr, + ctx.attr.pattern_link_stderr, + ) + marker_link_stdout = _check_each_message( + ctx, + cc_build_error_info.link_stdout, + ctx.file.matcher_link_stdout, + ctx.attr.pattern_link_stdout, + ) + markers = [ + marker_compile_stderr, + marker_compile_stdout, + marker_link_stderr, + marker_link_stdout, + ] + cc_build_error_info.markers + return [ + CcBuildErrorInfo( + compile_stderr = cc_build_error_info.compile_stderr, + compile_stdout = cc_build_error_info.compile_stdout, + link_stderr = cc_build_error_info.compile_stderr, + link_stdout = cc_build_error_info.compile_stdout, + markers = markers, + ), + DefaultInfo( + # Explicitly specify the markers to make sure the checking action is evaluated + files = depset(markers), + ), + ] + +_check_messages = rule( + implementation = _check_messages_impl, + attrs = { + "build_trial": attr.label( + doc = "`_try_build` target", + mandatory = True, + providers = [CcBuildErrorInfo], + ), + "matcher_compile_stderr": attr.label( + doc = "Matcher executable for stderr while compiling", + allow_single_file = True, + cfg = "exec", + executable = True, + mandatory = False, + ), + "matcher_compile_stdout": attr.label( + doc = "Matcher executable for stdout while compiling", + allow_single_file = True, + cfg = "exec", + executable = True, + mandatory = False, + ), + "matcher_link_stderr": attr.label( + doc = "Matcher executable for stderr while linking", + allow_single_file = True, + cfg = "exec", + executable = True, + mandatory = False, + ), + "matcher_link_stdout": attr.label( + doc = "Matcher executable for stdout while linking", + allow_single_file = True, + cfg = "exec", + executable = True, + mandatory = False, + ), + "pattern_compile_stderr": attr.string( + doc = "Pattern string for stderr while compiling", + mandatory = False, + ), + "pattern_compile_stdout": attr.string( + doc = "Pattern string for stdout while compiling", + mandatory = False, + ), + "pattern_link_stderr": attr.string( + doc = "Pattern string for stderr while linking", + mandatory = False, + ), + "pattern_link_stdout": attr.string( + doc = "Pattern string for stdout while linking", + mandatory = False, + ), + "_conditionally_execute": attr.label( + default = Label("//build_script:conditionally_execute"), + allow_single_file = True, + cfg = "exec", + executable = True, + ), + }, + provides = [CcBuildErrorInfo, DefaultInfo], +) + +_DEFAULT_MATCHER = struct( + matcher = None, + pattern = None, +) + +def cc_build_error( + *, + name, + compile_stderr = _DEFAULT_MATCHER, + compile_stdout = _DEFAULT_MATCHER, + link_stderr = _DEFAULT_MATCHER, + link_stdout = _DEFAULT_MATCHER, + **kwargs): + """Check a C/C++ build error. + + Args: + name(str): Name of the target. + compile_stderr(matcher struct): Matcher for stderr during compilation. + compile_stdout(matcher struct): Matcher for stdout during compilation. + link_stderr(matcher struct): Matcher for stderr while linking. + link_stdout(matcher struct): Matcher for stdout while linking. + **kwargs(dict): Passed to `_try_build`. + """ + + testonly = kwargs.pop("testonly", False) + tags = kwargs.pop("tags", []) + visibility = kwargs.pop("visibility", None) + + try_build_target = name + "__try_build" + _try_build( + name = try_build_target, + tags = ["manual"] + tags, + visibility = ["//visibility:private"], + testonly = testonly, + **kwargs + ) + + _check_messages( + name = name, + build_trial = ":" + try_build_target, + matcher_compile_stderr = compile_stderr.matcher, + matcher_compile_stdout = compile_stdout.matcher, + matcher_link_stderr = link_stderr.matcher, + matcher_link_stdout = link_stdout.matcher, + pattern_compile_stderr = compile_stderr.pattern, + pattern_compile_stdout = compile_stdout.pattern, + pattern_link_stderr = link_stderr.pattern, + pattern_link_stdout = link_stdout.pattern, + visibility = visibility, + tags = tags, + testonly = testonly, + ) diff --git a/lang/cc/defs.bzl b/lang/cc/defs.bzl new file mode 100644 index 0000000..0eebf94 --- /dev/null +++ b/lang/cc/defs.bzl @@ -0,0 +1,13 @@ +"""Public bzl file for re-exporting `//lang/cc` implementations. +""" + +load( + ":build_error.bzl", + _CcBuildErrorInfo = "CcBuildErrorInfo", + _cc_build_error = "cc_build_error", +) + +visibility("public") + +cc_build_error = _cc_build_error +CcBuildErrorInfo = _CcBuildErrorInfo diff --git a/matcher/BUILD.bazel b/matcher/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/matcher/defs.bzl b/matcher/defs.bzl new file mode 100644 index 0000000..ea74d26 --- /dev/null +++ b/matcher/defs.bzl @@ -0,0 +1,8 @@ +"""Public bzl file for re-exporting `//matcher` implementations. +""" + +load(":matcher.bzl", _matcher = "matcher") + +visibility("public") + +matcher = _matcher diff --git a/matcher/executable/BUILD.bazel b/matcher/executable/BUILD.bazel new file mode 100644 index 0000000..060602c --- /dev/null +++ b/matcher/executable/BUILD.bazel @@ -0,0 +1,21 @@ +"""Collection of matcher executables +""" + +package( + default_visibility = ["//visibility:public"], +) + +filegroup( + name = "contains_basic_regex", + srcs = ["contains_basic_regex.bash"], +) + +filegroup( + name = "contains_extended_regex", + srcs = ["contains_extended_regex.bash"], +) + +filegroup( + name = "has_substr", + srcs = ["has_substr.bash"], +) diff --git a/matcher/executable/contains_basic_regex.bash b/matcher/executable/contains_basic_regex.bash new file mode 100755 index 0000000..10a28e6 --- /dev/null +++ b/matcher/executable/contains_basic_regex.bash @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# +# Matcher executable for `contains_basic_regex`. +# +# Args: +# $1: Pattern string +# $2: Text file path to investigate + +set -euo pipefail +grep -Gq "$1" "$2" diff --git a/matcher/executable/contains_extended_regex.bash b/matcher/executable/contains_extended_regex.bash new file mode 100755 index 0000000..7f2c94d --- /dev/null +++ b/matcher/executable/contains_extended_regex.bash @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# +# Matcher executable for `contains_extended_regex`. +# +# Args: +# $1: Pattern string +# $2: Text file path to investigate + +set -euo pipefail +grep -Eq "$1" "$2" diff --git a/matcher/executable/has_substr.bash b/matcher/executable/has_substr.bash new file mode 100755 index 0000000..34dfc25 --- /dev/null +++ b/matcher/executable/has_substr.bash @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# +# Matcher executable for `has_substr`. +# +# Args: +# $1: Pattern string +# $2: Text file path to investigate + +set -euo pipefail +grep -Fq "$1" "$2" diff --git a/matcher/matcher.bzl b/matcher/matcher.bzl new file mode 100644 index 0000000..aa300c7 --- /dev/null +++ b/matcher/matcher.bzl @@ -0,0 +1,85 @@ +"""Defines `matcher`. + +Each matcher function must receive a positional string argument, +and return a struct with the following members: + pattern(str): Argument of the matcher function + matcher(label): Label to the matcher executable. + Matcher executable is an executables which receives two arguments + $1: Pattern string + $2: Text file path to investigate + and exits with an error if the pattern doesn't match the content of the test file. + +The matcher function is registered to the global struct `matcher`. The user uses it such as +```bazel + +cc_build_error( + name = "compile_error", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("error message pattern"), +) +``` +""" + +visibility("private") + +def _validate_pattern_string(pattern): + """Validate pattern string. + + Args: + pattern(str): Pattern string + """ + if not pattern: + fail( + "Empty pattern string is not allowed for the matcher", + ) + +def _contains_basic_regex(pattern): + """Matcher to check if the target contains a basic regular expression. + + Args: + pattern(str): Pattern for substring. + + Return: + struct describing the matcher + """ + _validate_pattern_string(pattern) + return struct( + pattern = pattern, + matcher = Label("//matcher/executable:contains_basic_regex"), + ) + +def _contains_extended_regex(pattern): + """Matcher to check if the target contains a extended regular expression. + + Args: + pattern(str): Pattern for substring. + + Return: + struct describing the matcher + """ + _validate_pattern_string(pattern) + return struct( + pattern = pattern, + matcher = Label("//matcher/executable:contains_extended_regex"), + ) + +def _has_substr(pattern): + """Matcher to check if the target has a substring. + + Args: + pattern(str): Pattern for substring. + + Return: + struct describing the matcher + """ + _validate_pattern_string(pattern) + return struct( + pattern = pattern, + matcher = Label("//matcher/executable:has_substr"), + ) + +matcher = struct( + contains_basic_regex = _contains_basic_regex, + contains_extended_regex = _contains_extended_regex, + has_substr = _has_substr, +) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/tests/cc/BUILD.bazel b/tests/cc/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/tests/cc/c_compile_error/BUILD.bazel b/tests/cc/c_compile_error/BUILD.bazel new file mode 100644 index 0000000..9185521 --- /dev/null +++ b/tests/cc/c_compile_error/BUILD.bazel @@ -0,0 +1,36 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_build_error( + name = "plain", + src = "c_compile_error.c", +) + +cc_build_error( + name = "with_substr_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.has_substr("for c_compile_error.c"), +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.contains_basic_regex(r"for[[:space:]]c_compile_error\.\(c\|C\)"), +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.contains_extended_regex(r"for[[:space:]]c_compile_error\.(c|C)"), +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/c_compile_error/c_compile_error.c b/tests/cc/c_compile_error/c_compile_error.c new file mode 100644 index 0000000..7ee8b64 --- /dev/null +++ b/tests/cc/c_compile_error/c_compile_error.c @@ -0,0 +1,6 @@ +#include + +int main() +{ + static_assert(0, "Compile error message for c_compile_error.c"); +} diff --git a/tests/cc/c_link_error/BUILD.bazel b/tests/cc/c_link_error/BUILD.bazel new file mode 100644 index 0000000..911d165 --- /dev/null +++ b/tests/cc/c_link_error/BUILD.bazel @@ -0,0 +1,36 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_build_error( + name = "plain", + src = "c_link_error.c", +) + +cc_build_error( + name = "with_substr_matcher", + src = "c_link_error.c", + link_stderr = matcher.has_substr("GetReturnValue"), +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "c_link_error.c", + link_stderr = matcher.contains_basic_regex(r"undefined.*GetReturnValue"), +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "c_link_error.c", + link_stderr = matcher.contains_extended_regex(r"undefined.*GetReturnValue"), +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/c_link_error/c_link_error.c b/tests/cc/c_link_error/c_link_error.c new file mode 100644 index 0000000..d5afc46 --- /dev/null +++ b/tests/cc/c_link_error/c_link_error.c @@ -0,0 +1,6 @@ +int GetReturnValue(); + +int main() +{ + return GetReturnValue(); +} diff --git a/tests/cc/cpp_compile_error/BUILD.bazel b/tests/cc/cpp_compile_error/BUILD.bazel new file mode 100644 index 0000000..560b33e --- /dev/null +++ b/tests/cc/cpp_compile_error/BUILD.bazel @@ -0,0 +1,36 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_build_error( + name = "plain", + src = "cpp_compile_error.cpp", +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("for cpp_compile_error.cpp"), +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_basic_regex(r"for[[:space:]]cpp_compile_error\.\(cpp\|cxx\)"), +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_extended_regex(r"for[[:space:]]cpp_compile_error\.(cpp|cxx)"), +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_compile_error/cpp_compile_error.cpp b/tests/cc/cpp_compile_error/cpp_compile_error.cpp new file mode 100644 index 0000000..404e1bf --- /dev/null +++ b/tests/cc/cpp_compile_error/cpp_compile_error.cpp @@ -0,0 +1,4 @@ +int main() +{ + static_assert(false, "Compile error message for cpp_compile_error.cpp"); +} diff --git a/tests/cc/cpp_link_error/BUILD.bazel b/tests/cc/cpp_link_error/BUILD.bazel new file mode 100644 index 0000000..7adeb57 --- /dev/null +++ b/tests/cc/cpp_link_error/BUILD.bazel @@ -0,0 +1,36 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_build_error( + name = "plain", + src = "cpp_link_error.cpp", +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.has_substr("GetReturnValue"), +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.contains_basic_regex(r"undefined.*GetReturnValue"), +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.contains_extended_regex(r"undefined.*GetReturnValue"), +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_link_error/cpp_link_error.cpp b/tests/cc/cpp_link_error/cpp_link_error.cpp new file mode 100644 index 0000000..d5afc46 --- /dev/null +++ b/tests/cc/cpp_link_error/cpp_link_error.cpp @@ -0,0 +1,6 @@ +int GetReturnValue(); + +int main() +{ + return GetReturnValue(); +} From 9fd8af7292cc4a45e72289acebbcfb2c4725436c Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 12:54:10 +0900 Subject: [PATCH 02/14] add test cases with `cc_library` --- tests/cc/c_compile_error/c_compile_error.c | 1 + .../cc/c_compile_error_with_deps/BUILD.bazel | 46 +++++++++++++++++++ .../c_compile_error.c | 6 +++ tests/cc/c_compile_error_with_deps/library.c | 7 +++ tests/cc/c_compile_error_with_deps/library.h | 6 +++ tests/cc/c_link_error_with_deps/BUILD.bazel | 46 +++++++++++++++++++ .../cc/c_link_error_with_deps/c_link_error.c | 6 +++ tests/cc/c_link_error_with_deps/library.c | 0 tests/cc/c_link_error_with_deps/library.h | 6 +++ .../cpp_compile_error/cpp_compile_error.cpp | 1 + 10 files changed, 125 insertions(+) create mode 100644 tests/cc/c_compile_error_with_deps/BUILD.bazel create mode 100644 tests/cc/c_compile_error_with_deps/c_compile_error.c create mode 100644 tests/cc/c_compile_error_with_deps/library.c create mode 100644 tests/cc/c_compile_error_with_deps/library.h create mode 100644 tests/cc/c_link_error_with_deps/BUILD.bazel create mode 100644 tests/cc/c_link_error_with_deps/c_link_error.c create mode 100644 tests/cc/c_link_error_with_deps/library.c create mode 100644 tests/cc/c_link_error_with_deps/library.h diff --git a/tests/cc/c_compile_error/c_compile_error.c b/tests/cc/c_compile_error/c_compile_error.c index 7ee8b64..c967d31 100644 --- a/tests/cc/c_compile_error/c_compile_error.c +++ b/tests/cc/c_compile_error/c_compile_error.c @@ -3,4 +3,5 @@ int main() { static_assert(0, "Compile error message for c_compile_error.c"); + return 0; } diff --git a/tests/cc/c_compile_error_with_deps/BUILD.bazel b/tests/cc/c_compile_error_with_deps/BUILD.bazel new file mode 100644 index 0000000..f684e8a --- /dev/null +++ b/tests/cc/c_compile_error_with_deps/BUILD.bazel @@ -0,0 +1,46 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_library( + name = "library", + srcs = ["library.c"], + hdrs = ["library.h"], +) + +cc_build_error( + name = "plain", + src = "c_compile_error.c", + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.has_substr("too many arguments"), + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.contains_basic_regex("too many arguments.*argument"), + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "c_compile_error.c", + compile_stderr = matcher.contains_extended_regex("too many arguments.*argument"), + deps = [":library"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/c_compile_error_with_deps/c_compile_error.c b/tests/cc/c_compile_error_with_deps/c_compile_error.c new file mode 100644 index 0000000..74129f4 --- /dev/null +++ b/tests/cc/c_compile_error_with_deps/c_compile_error.c @@ -0,0 +1,6 @@ +#include "library.h" + +int main() +{ + return FunctionWithOneParameter(1,2,3,4,5); +} diff --git a/tests/cc/c_compile_error_with_deps/library.c b/tests/cc/c_compile_error_with_deps/library.c new file mode 100644 index 0000000..ac4b884 --- /dev/null +++ b/tests/cc/c_compile_error_with_deps/library.c @@ -0,0 +1,7 @@ +#include "library.h" +#include + +int FunctionWithOneParameter(int value) +{ + return 123; +} diff --git a/tests/cc/c_compile_error_with_deps/library.h b/tests/cc/c_compile_error_with_deps/library.h new file mode 100644 index 0000000..ce8e1e7 --- /dev/null +++ b/tests/cc/c_compile_error_with_deps/library.h @@ -0,0 +1,6 @@ +#ifndef TESTS_CC_C_COMPILE_ERROR_WITH_DEPS_ASSERTION_H_ +#define TESTS_CC_C_COMPILE_ERROR_WITH_DEPS_ASSERTION_H_ + +int FunctionWithOneParameter(int value); + +#endif // TESTS_CC_C_COMPILE_ERROR_WITH_DEPS_ASSERTION_H_ diff --git a/tests/cc/c_link_error_with_deps/BUILD.bazel b/tests/cc/c_link_error_with_deps/BUILD.bazel new file mode 100644 index 0000000..397f721 --- /dev/null +++ b/tests/cc/c_link_error_with_deps/BUILD.bazel @@ -0,0 +1,46 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_library( + name = "library", + srcs = ["library.c"], + hdrs = ["library.h"], +) + +cc_build_error( + name = "plain", + src = "c_link_error.c", + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "c_link_error.c", + link_stderr = matcher.has_substr("FunctionWithoutDefinition"), + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "c_link_error.c", + link_stderr = matcher.contains_basic_regex(r"undefined.*FunctionWithoutDefinition"), + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "c_link_error.c", + link_stderr = matcher.contains_extended_regex(r"undefined.*FunctionWithoutDefinition"), + deps = [":library"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/c_link_error_with_deps/c_link_error.c b/tests/cc/c_link_error_with_deps/c_link_error.c new file mode 100644 index 0000000..abcf034 --- /dev/null +++ b/tests/cc/c_link_error_with_deps/c_link_error.c @@ -0,0 +1,6 @@ +#include "library.h" + +int main() +{ + return FunctionWithoutDefinition(); +} diff --git a/tests/cc/c_link_error_with_deps/library.c b/tests/cc/c_link_error_with_deps/library.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/cc/c_link_error_with_deps/library.h b/tests/cc/c_link_error_with_deps/library.h new file mode 100644 index 0000000..302856e --- /dev/null +++ b/tests/cc/c_link_error_with_deps/library.h @@ -0,0 +1,6 @@ +#ifndef TESTS_CC_C_LINK_ERROR_WITH_DEPS_ASSERTION_H_ +#define TESTS_CC_C_LINK_ERROR_WITH_DEPS_ASSERTION_H_ + +int FunctionWithoutDefinition(); + +#endif // TESTS_CC_C_LINK_ERROR_WITH_DEPS_ASSERTION_H_ diff --git a/tests/cc/cpp_compile_error/cpp_compile_error.cpp b/tests/cc/cpp_compile_error/cpp_compile_error.cpp index 404e1bf..4e08463 100644 --- a/tests/cc/cpp_compile_error/cpp_compile_error.cpp +++ b/tests/cc/cpp_compile_error/cpp_compile_error.cpp @@ -1,4 +1,5 @@ int main() { static_assert(false, "Compile error message for cpp_compile_error.cpp"); + return 0; } From 8fdf36e225178286dbcd611235f068d4891c59d7 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 13:15:35 +0900 Subject: [PATCH 03/14] tests with deps in C++ --- tests/cc/c_compile_error_with_deps/library.c | 3 +- .../cpp_compile_error_with_deps/BUILD.bazel | 46 +++++++++++++++++++ .../cpp_compile_error.cpp | 6 +++ .../cpp_compile_error_with_deps/library.cpp | 6 +++ .../cpp_compile_error_with_deps/library.hpp | 6 +++ tests/cc/cpp_link_error_with_deps/BUILD.bazel | 46 +++++++++++++++++++ .../cpp_link_error.cpp | 6 +++ tests/cc/cpp_link_error_with_deps/library.cpp | 0 tests/cc/cpp_link_error_with_deps/library.hpp | 6 +++ 9 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 tests/cc/cpp_compile_error_with_deps/BUILD.bazel create mode 100644 tests/cc/cpp_compile_error_with_deps/cpp_compile_error.cpp create mode 100644 tests/cc/cpp_compile_error_with_deps/library.cpp create mode 100644 tests/cc/cpp_compile_error_with_deps/library.hpp create mode 100644 tests/cc/cpp_link_error_with_deps/BUILD.bazel create mode 100644 tests/cc/cpp_link_error_with_deps/cpp_link_error.cpp create mode 100644 tests/cc/cpp_link_error_with_deps/library.cpp create mode 100644 tests/cc/cpp_link_error_with_deps/library.hpp diff --git a/tests/cc/c_compile_error_with_deps/library.c b/tests/cc/c_compile_error_with_deps/library.c index ac4b884..b0454b2 100644 --- a/tests/cc/c_compile_error_with_deps/library.c +++ b/tests/cc/c_compile_error_with_deps/library.c @@ -1,7 +1,6 @@ #include "library.h" -#include int FunctionWithOneParameter(int value) { - return 123; + return 123; } diff --git a/tests/cc/cpp_compile_error_with_deps/BUILD.bazel b/tests/cc/cpp_compile_error_with_deps/BUILD.bazel new file mode 100644 index 0000000..41ac6ef --- /dev/null +++ b/tests/cc/cpp_compile_error_with_deps/BUILD.bazel @@ -0,0 +1,46 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_library( + name = "library", + srcs = ["library.cpp"], + hdrs = ["library.hpp"], +) + +cc_build_error( + name = "plain", + src = "cpp_compile_error.cpp", + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("requires single argument"), + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_basic_regex("requires single argument.*argument"), + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_extended_regex("requires single argument.*argument"), + deps = [":library"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_compile_error_with_deps/cpp_compile_error.cpp b/tests/cc/cpp_compile_error_with_deps/cpp_compile_error.cpp new file mode 100644 index 0000000..f35782f --- /dev/null +++ b/tests/cc/cpp_compile_error_with_deps/cpp_compile_error.cpp @@ -0,0 +1,6 @@ +#include "library.hpp" + +int main() +{ + return FunctionWithOneParameter(1, 2, 3, 4, 5); +} diff --git a/tests/cc/cpp_compile_error_with_deps/library.cpp b/tests/cc/cpp_compile_error_with_deps/library.cpp new file mode 100644 index 0000000..7c0a177 --- /dev/null +++ b/tests/cc/cpp_compile_error_with_deps/library.cpp @@ -0,0 +1,6 @@ +#include "library.hpp" + +int FunctionWithOneParameter(int value) +{ + return 123; +} diff --git a/tests/cc/cpp_compile_error_with_deps/library.hpp b/tests/cc/cpp_compile_error_with_deps/library.hpp new file mode 100644 index 0000000..e63cfce --- /dev/null +++ b/tests/cc/cpp_compile_error_with_deps/library.hpp @@ -0,0 +1,6 @@ +#ifndef TESTS_CC_CPP_COMPILE_ERROR_WITH_DEPS_ASSERTION_HPP_ +#define TESTS_CC_CPP_COMPILE_ERROR_WITH_DEPS_ASSERTION_HPP_ + +int FunctionWithOneParameter(int value); + +#endif // TESTS_CC_CPP_COMPILE_ERROR_WITH_DEPS_ASSERTION_HPP_ diff --git a/tests/cc/cpp_link_error_with_deps/BUILD.bazel b/tests/cc/cpp_link_error_with_deps/BUILD.bazel new file mode 100644 index 0000000..8443099 --- /dev/null +++ b/tests/cc/cpp_link_error_with_deps/BUILD.bazel @@ -0,0 +1,46 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_library( + name = "library", + srcs = ["library.cpp"], + hdrs = ["library.hpp"], +) + +cc_build_error( + name = "plain", + src = "cpp_link_error.cpp", + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.has_substr("FunctionWithoutDefinition"), + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.contains_basic_regex(r"undefined.*FunctionWithoutDefinition"), + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_link_error.cpp", + link_stderr = matcher.contains_extended_regex(r"undefined.*FunctionWithoutDefinition"), + deps = [":library"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_link_error_with_deps/cpp_link_error.cpp b/tests/cc/cpp_link_error_with_deps/cpp_link_error.cpp new file mode 100644 index 0000000..74e369c --- /dev/null +++ b/tests/cc/cpp_link_error_with_deps/cpp_link_error.cpp @@ -0,0 +1,6 @@ +#include "library.hpp" + +int main() +{ + return FunctionWithoutDefinition(); +} diff --git a/tests/cc/cpp_link_error_with_deps/library.cpp b/tests/cc/cpp_link_error_with_deps/library.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tests/cc/cpp_link_error_with_deps/library.hpp b/tests/cc/cpp_link_error_with_deps/library.hpp new file mode 100644 index 0000000..e3f5c72 --- /dev/null +++ b/tests/cc/cpp_link_error_with_deps/library.hpp @@ -0,0 +1,6 @@ +#ifndef TESTS_CC_CPP_LINK_ERROR_WITH_DEPS_ASSERTION_HPP_ +#define TESTS_CC_CPP_LINK_ERROR_WITH_DEPS_ASSERTION_HPP_ + +int FunctionWithoutDefinition(); + +#endif // TESTS_CC_CPP_LINK_ERROR_WITH_DEPS_ASSERTION_HPP_ From 8bbddd7f022053558e58254527d64ad8da9eadf4 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 14:29:56 +0900 Subject: [PATCH 04/14] add readme files --- README.md | 30 ++++++++++++++++++++++++++++-- execute_tests.sh | 6 ++++++ lang/cc/README.md | 27 +++++++++++++++++++++++++++ lang/cc/build_error.bzl | 3 ++- matcher/README.md | 13 +++++++++++++ matcher/matcher.bzl | 10 ---------- 6 files changed, 76 insertions(+), 13 deletions(-) create mode 100755 execute_tests.sh create mode 100644 lang/cc/README.md create mode 100644 matcher/README.md diff --git a/README.md b/README.md index 081d9f5..2ee4f8c 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,38 @@ Bazel implementations to test a build error. There's a situation where a developer wants to test if particular code doesn't compile. However, when using ordinary testing rules, such as `cc_test`, `bazel test` results in an error if the test code doesn't compile. -`rules_build_error` is the repository to address such an issue. It provides some implementations to test the compilation error for each programming language. When the code written in a particular **does** compile, `bazel build` should fail. +`rules_build_error` is the repository to address such a problem. It provides some implementations to test the compilation error for each programming language. When the code written in a particular **does** compile, `bazel build` should fail for the associated target. ## Usage +### C/C++ usage + +```bazel +load("@rules_build_error//lang/cc:defs.bzl", "cc_build_error") +load("@rules_build_error//matcher:defs.bzl", "matcher") + +cc_build_error( + name = "cause_compile_error", + src = "cause_compile_error.cpp", + deps = [":library_to_successfully_link"], # `:library_to_successfully_link` must provide `CcInfo`, like `cc_library` + compile_stderr = matcher.has_substr("static assertion failed"), +) +``` + ## Language-specific implementations -### C/C++ +The implementations to check the build error in a particular language is available. + +### C/C++ implementation + +Refer to [its readme](lang/cc/README.md) ## Matcher + +In order to specify how to validate the error message, a struct `matcher` is available. Refer to [its readme](matcher/README.md) for more details. + +## Development + +### How to test + +Execute [`execute_tests.sh`](execute_tests.sh) after installing [`bazelisk`](https://github.com/bazelbuild/bazelisk). diff --git a/execute_tests.sh b/execute_tests.sh new file mode 100755 index 0000000..854c8d8 --- /dev/null +++ b/execute_tests.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# +# Execute all tests + +set -euo pipefail +bazelisk test //... diff --git a/lang/cc/README.md b/lang/cc/README.md new file mode 100644 index 0000000..c23ff49 --- /dev/null +++ b/lang/cc/README.md @@ -0,0 +1,27 @@ +# C/C++ build error + +Defines some implementations to check build error in C/C++. + +## `cc_build_error` + +`cc_build_error` is a wrapper macro for rules, providing [`CcBuildErrorInfo`](#ccbuilderrorinfo). + +In addition to the common rule attributes listed [here](https://bazel.build/reference/be/common-definitions#common-attributes), it can receive the following attributes (regarding the specific matcher, please refer to [its readme](../../matcher/README.md)): + +| Attribute | Description | Type | Is this attribute required? | Other constraints | +| ------------------------ | ------------------------------------------------------------------ | ---------------- | --------------------------- | --------------------------------------------------- | +| name | Name of the target. | str | Yes | | +| src | C/C++ source file to check build | label | Yes | Must be a single file having an extension for C/C++ | +| additional_linker_inputs | Pass these files to the linker command | list of labels | No (defaults to `[]`) | | +| copts | C/C++ compilation options | list of str | No (defaults to `[]`) | | +| deps | The list of CcInfo libraries to be linked in to the binary target. | list of label | No (defaults to `[]`) | Each list element must provide `CcInfo` | +| linkopts | C/C++ linking options | list of str | No (defaults to `[]`) | | +| local_defines | Pre-processor macro definitions | list of str | No (defaults to `[]`) | | +| compile_stderr | Matcher for the stderr message while compiling | specific matcher | No (defaults to no-op) | | +| compile_stdout | Matcher for the stdout message while compiling | specific matcher | No (defaults to no-op) | | +| link_stderr | Matcher for the stderr message while compiling | specific matcher | No (defaults to no-op) | | +| link_stdout | Matcher for the stdout message while compiling | specific matcher | No (defaults to no-op) | | + +## `CcBuildErrorInfo` + +`CcBuildErrorInfo` is a provider describing the build error in C/C++. See its definition in [its bzl file](./build_error.bzl) for its details. diff --git a/lang/cc/build_error.bzl b/lang/cc/build_error.bzl index f653b1d..04c4c14 100644 --- a/lang/cc/build_error.bzl +++ b/lang/cc/build_error.bzl @@ -335,6 +335,7 @@ _try_build = rule( "additional_linker_inputs": attr.label_list( doc = "Pass these files to the linker command", allow_empty = True, + allow_files = True, mandatory = False, ), "copts": attr.string_list( @@ -343,7 +344,7 @@ _try_build = rule( mandatory = False, ), "deps": attr.label_list( - doc = "The list of CcInfo libraries to be linked in to the binary target.", + doc = "The list of CcInfo libraries to be linked in to the target", allow_empty = True, mandatory = False, providers = [CcInfo], diff --git a/matcher/README.md b/matcher/README.md new file mode 100644 index 0000000..c1fe6f8 --- /dev/null +++ b/matcher/README.md @@ -0,0 +1,13 @@ +# Matcher + +Defines a struct `matcher`. + +`matcher` has some member functions each of which receives a pattern string as a positional string argument, and which returns its specific matcher. Each specific matcher can be used to specify the way of validating the build error message (stderr or stdout). + +The member functions of `matcher` are as follows + +| Member | Description | +| ----------------------- | --------------------------------------------------------------------- | +| contains_basic_regex | Check if the message contains the basic regular expression pattern | +| contains_extended_regex | Check if the message contains the extended regular expression pattern | +| has_substr | Check if the message has the sub-string | diff --git a/matcher/matcher.bzl b/matcher/matcher.bzl index aa300c7..19b79f0 100644 --- a/matcher/matcher.bzl +++ b/matcher/matcher.bzl @@ -8,16 +8,6 @@ and return a struct with the following members: $1: Pattern string $2: Text file path to investigate and exits with an error if the pattern doesn't match the content of the test file. - -The matcher function is registered to the global struct `matcher`. The user uses it such as -```bazel - -cc_build_error( - name = "compile_error", - src = "cpp_compile_error.cpp", - compile_stderr = matcher.has_substr("error message pattern"), -) -``` """ visibility("private") From 05a61b83d5b9c443760b7934e1cfc721a6fcc849 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 14:37:39 +0900 Subject: [PATCH 05/14] update readme a little --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ee4f8c..65f789c 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,9 @@ In order to specify how to validate the error message, a struct `matcher` is ava ### How to test -Execute [`execute_tests.sh`](execute_tests.sh) after installing [`bazelisk`](https://github.com/bazelbuild/bazelisk). +Execute [`execute_tests.sh`](execute_tests.sh) after installing [`bazelisk`](https://github.com/bazelbuild/bazelisk). It executes `bazelisk test` and `bazelisk build` commands under the hood. + +When writing tests, in principle, do the following things + +- Use `tags = ["manual", "fail-to-build"]` if a test case target must fail to build with `bazelisk build` +- Otherwise, do not use any of the tags above for the target. From 6546b0eae8ee68a31e45717ac4d34b65125024b6 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:02:46 +0900 Subject: [PATCH 06/14] add tests for build success --- README.md | 5 +-- execute_tests.sh | 32 +++++++++++++++ tests/cc/cpp_successful_build/BUILD.bazel | 29 ++++++++++++++ .../cpp_successful_build.cpp | 4 ++ .../BUILD.bazel | 40 +++++++++++++++++++ .../cpp_compile_error.cpp | 6 +++ .../library.cpp | 6 +++ .../library.hpp | 6 +++ 8 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 tests/cc/cpp_successful_build/BUILD.bazel create mode 100644 tests/cc/cpp_successful_build/cpp_successful_build.cpp create mode 100644 tests/cc/cpp_successful_build_with_deps/BUILD.bazel create mode 100644 tests/cc/cpp_successful_build_with_deps/cpp_compile_error.cpp create mode 100644 tests/cc/cpp_successful_build_with_deps/library.cpp create mode 100644 tests/cc/cpp_successful_build_with_deps/library.hpp diff --git a/README.md b/README.md index 65f789c..41161d3 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,4 @@ In order to specify how to validate the error message, a struct `matcher` is ava Execute [`execute_tests.sh`](execute_tests.sh) after installing [`bazelisk`](https://github.com/bazelbuild/bazelisk). It executes `bazelisk test` and `bazelisk build` commands under the hood. -When writing tests, in principle, do the following things - -- Use `tags = ["manual", "fail-to-build"]` if a test case target must fail to build with `bazelisk build` -- Otherwise, do not use any of the tags above for the target. +When writing tests, in principle, use `tags = ["manual"]` if a test case target must fail with `bazelisk test`. In such a test case, confirm its failure in [`execute_tests.sh`](execute_tests.sh) one by one. diff --git a/execute_tests.sh b/execute_tests.sh index 854c8d8..a6d5d7a 100755 --- a/execute_tests.sh +++ b/execute_tests.sh @@ -3,4 +3,36 @@ # Execute all tests set -euo pipefail + +check_bazel_build_error() { + # Check bazel build error for a particular target + # + # Args: + # $1: label to check + local label + label=$1 + + # Before executing `bazelisk build`, check if the target exists with `bazelisk query` + bazelisk query "${label}" + + # Check build error + if bazelisk build "${label}"; then + echo "Target '${label}' must fail to build, but succeeded" >&2 + exit 1 + else + echo "OK! It has failed as intended." + fi +} + +echo "Executing the test cases which should pass straightforward 'bazelisk test'" bazelisk test //... + +echo "Executing the test cases which should fail at 'bazelisk build'" +check_bazel_build_error //tests/cc/cpp_successful_build:plain +check_bazel_build_error //tests/cc/cpp_successful_build:with_basic_regex_matcher +check_bazel_build_error //tests/cc/cpp_successful_build:with_extended_regex_matcher +check_bazel_build_error //tests/cc/cpp_successful_build:with_substr_matcher +check_bazel_build_error //tests/cc/cpp_successful_build_with_deps:plain +check_bazel_build_error //tests/cc/cpp_successful_build_with_deps:with_basic_regex_matcher +check_bazel_build_error //tests/cc/cpp_successful_build_with_deps:with_extended_regex_matcher +check_bazel_build_error //tests/cc/cpp_successful_build_with_deps:with_substr_matcher diff --git a/tests/cc/cpp_successful_build/BUILD.bazel b/tests/cc/cpp_successful_build/BUILD.bazel new file mode 100644 index 0000000..3d514ae --- /dev/null +++ b/tests/cc/cpp_successful_build/BUILD.bazel @@ -0,0 +1,29 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") + +cc_build_error( + name = "plain", + src = "cpp_successful_build.cpp", + tags = ["manual"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_successful_build.cpp", + compile_stderr = matcher.has_substr("for cpp_successful_build.cpp"), + tags = ["manual"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_successful_build.cpp", + compile_stderr = matcher.contains_basic_regex(r"for[[:space:]]cpp_successful_build\.\(cpp\|cxx\)"), + tags = ["manual"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_successful_build.cpp", + compile_stderr = matcher.contains_extended_regex(r"for[[:space:]]cpp_successful_build\.(cpp|cxx)"), + tags = ["manual"], +) diff --git a/tests/cc/cpp_successful_build/cpp_successful_build.cpp b/tests/cc/cpp_successful_build/cpp_successful_build.cpp new file mode 100644 index 0000000..905869d --- /dev/null +++ b/tests/cc/cpp_successful_build/cpp_successful_build.cpp @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/tests/cc/cpp_successful_build_with_deps/BUILD.bazel b/tests/cc/cpp_successful_build_with_deps/BUILD.bazel new file mode 100644 index 0000000..4b728ee --- /dev/null +++ b/tests/cc/cpp_successful_build_with_deps/BUILD.bazel @@ -0,0 +1,40 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") + +cc_library( + name = "library", + srcs = ["library.cpp"], + hdrs = ["library.hpp"], + tags = ["manual"], +) + +cc_build_error( + name = "plain", + src = "cpp_compile_error.cpp", + tags = ["manual"], + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("requires single argument"), + tags = ["manual"], + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_basic_regex("requires single argument.*argument"), + tags = ["manual"], + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_extended_regex("requires single argument.*argument"), + tags = ["manual"], + deps = [":library"], +) diff --git a/tests/cc/cpp_successful_build_with_deps/cpp_compile_error.cpp b/tests/cc/cpp_successful_build_with_deps/cpp_compile_error.cpp new file mode 100644 index 0000000..bdf1ef6 --- /dev/null +++ b/tests/cc/cpp_successful_build_with_deps/cpp_compile_error.cpp @@ -0,0 +1,6 @@ +#include "library.hpp" + +int main() +{ + return NormalFunction(1); +} diff --git a/tests/cc/cpp_successful_build_with_deps/library.cpp b/tests/cc/cpp_successful_build_with_deps/library.cpp new file mode 100644 index 0000000..f899529 --- /dev/null +++ b/tests/cc/cpp_successful_build_with_deps/library.cpp @@ -0,0 +1,6 @@ +#include "library.hpp" + +int NormalFunction(int value) +{ + return 123 + value; +} diff --git a/tests/cc/cpp_successful_build_with_deps/library.hpp b/tests/cc/cpp_successful_build_with_deps/library.hpp new file mode 100644 index 0000000..585edee --- /dev/null +++ b/tests/cc/cpp_successful_build_with_deps/library.hpp @@ -0,0 +1,6 @@ +#ifndef TESTS_CC_CPP_SUCCESSFUL_BUILD_WITH_DEPS_ASSERTION_HPP_ +#define TESTS_CC_CPP_SUCCESSFUL_BUILD_WITH_DEPS_ASSERTION_HPP_ + +int NormalFunction(int value); + +#endif // TESTS_CC_CPP_SUCCESSFUL_BUILD_WITH_DEPS_ASSERTION_HPP_ From 7a8a7a5f2df5f683ac7ba3c6da59c8b996451702 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:10:56 +0900 Subject: [PATCH 07/14] add a test: tests/cc/cpp_compile_error_with_local_defines --- .../BUILD.bazel | 40 +++++++++++++++++++ .../cpp_compile_error.cpp | 8 ++++ 2 files changed, 48 insertions(+) create mode 100644 tests/cc/cpp_compile_error_with_local_defines/BUILD.bazel create mode 100644 tests/cc/cpp_compile_error_with_local_defines/cpp_compile_error.cpp diff --git a/tests/cc/cpp_compile_error_with_local_defines/BUILD.bazel b/tests/cc/cpp_compile_error_with_local_defines/BUILD.bazel new file mode 100644 index 0000000..eaabd2f --- /dev/null +++ b/tests/cc/cpp_compile_error_with_local_defines/BUILD.bazel @@ -0,0 +1,40 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_build_error( + name = "plain", + src = "cpp_compile_error.cpp", + local_defines = ["MACRO_IN_LOCAL_DEFINES"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("With local_defines, "), + local_defines = ["MACRO_IN_LOCAL_DEFINES"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_basic_regex("With local_defines, this.*"), + local_defines = ["MACRO_IN_LOCAL_DEFINES"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_extended_regex("With local_defines, this.*"), + local_defines = ["MACRO_IN_LOCAL_DEFINES"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_compile_error_with_local_defines/cpp_compile_error.cpp b/tests/cc/cpp_compile_error_with_local_defines/cpp_compile_error.cpp new file mode 100644 index 0000000..7d734fe --- /dev/null +++ b/tests/cc/cpp_compile_error_with_local_defines/cpp_compile_error.cpp @@ -0,0 +1,8 @@ +int main() +{ + +#ifdef MACRO_IN_LOCAL_DEFINES + static_assert(false, "With local_defines, this error must show up"); +#endif + return 0; +} From 6920cd9ce6e2ae9755abfe6ffda5871936f8304e Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:17:55 +0900 Subject: [PATCH 08/14] add `tests/cc/cpp_compile_error_defines_is_transitive` --- .../BUILD.bazel | 45 +++++++++++++++++++ .../cpp_compile_error.cpp | 8 ++++ 2 files changed, 53 insertions(+) create mode 100644 tests/cc/cpp_compile_error_defines_is_transitive/BUILD.bazel create mode 100644 tests/cc/cpp_compile_error_defines_is_transitive/cpp_compile_error.cpp diff --git a/tests/cc/cpp_compile_error_defines_is_transitive/BUILD.bazel b/tests/cc/cpp_compile_error_defines_is_transitive/BUILD.bazel new file mode 100644 index 0000000..4ade5b5 --- /dev/null +++ b/tests/cc/cpp_compile_error_defines_is_transitive/BUILD.bazel @@ -0,0 +1,45 @@ +load("//lang/cc:defs.bzl", "cc_build_error") +load("//matcher:defs.bzl", "matcher") +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +cc_library( + name = "library", + defines = ["MACRO_IN_DEFINES"], +) + +cc_build_error( + name = "plain", + src = "cpp_compile_error.cpp", + deps = [":library"], +) + +cc_build_error( + name = "with_substr_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.has_substr("With transitive defines, "), + deps = [":library"], +) + +cc_build_error( + name = "with_basic_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_basic_regex("With transitive defines, this.*"), + deps = [":library"], +) + +cc_build_error( + name = "with_extended_regex_matcher", + src = "cpp_compile_error.cpp", + compile_stderr = matcher.contains_extended_regex("With transitive defines, this.*"), + deps = [":library"], +) + +build_test( + name = "build_test", + targets = [ + ":plain", + ":with_substr_matcher", + ":with_basic_regex_matcher", + ":with_extended_regex_matcher", + ], +) diff --git a/tests/cc/cpp_compile_error_defines_is_transitive/cpp_compile_error.cpp b/tests/cc/cpp_compile_error_defines_is_transitive/cpp_compile_error.cpp new file mode 100644 index 0000000..5b160f8 --- /dev/null +++ b/tests/cc/cpp_compile_error_defines_is_transitive/cpp_compile_error.cpp @@ -0,0 +1,8 @@ +int main() +{ + +#ifdef MACRO_IN_DEFINES + static_assert(false, "With transitive defines, this error must show up"); +#endif + return 0; +} From 11370fc1250cdfbea4906882fe5b612159563f44 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:49:20 +0900 Subject: [PATCH 09/14] try github actions --- .github/workflows/unit-tests.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/unit-tests.yaml diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml new file mode 100644 index 0000000..06be9cd --- /dev/null +++ b/.github/workflows/unit-tests.yaml @@ -0,0 +1,26 @@ +--- +name: Run unit tests +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit-tests: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + - name: Install bazelisk + run: | + bazelisk_dir="$(realpath "$(mktemp -d -p .)")" + wget https://github.com/bazelbuild/bazelisk/releases/download/v1.19.0/bazelisk-linux-amd64 \ + -O "${bazelisk_dir}/bazelisk" + chmod +x "${bazelisk_dir}/bazelisk" + echo "${bazelisk_dir}" >> "${GITHUB_PATH}" + - name: Run unit tests + run: ./execute_tests.sh From a5edccc88de2628317a8d9c2ad7311200377fdf3 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:52:59 +0900 Subject: [PATCH 10/14] modify `on` --- .github/workflows/unit-tests.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 06be9cd..bde2fc3 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -1,7 +1,6 @@ --- name: Run unit tests -on: - pull_request: +on: push concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 0d6c10ce2baf2e743e2a81b56d5075f021045b64 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:55:30 +0900 Subject: [PATCH 11/14] update runner --- .github/workflows/unit-tests.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index bde2fc3..ea728ca 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -1,6 +1,7 @@ --- name: Run unit tests -on: push +on: + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -8,7 +9,7 @@ concurrency: jobs: unit-tests: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 From b8701c94fa1d346414d7d42b21f1f5b024070ae8 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 15:58:24 +0900 Subject: [PATCH 12/14] modify `on` --- .github/workflows/unit-tests.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index ea728ca..a2c38a7 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -1,7 +1,6 @@ --- name: Run unit tests -on: - pull_request: +on: push concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 0905ea0676438aa417f164d47dcea66f1df9313a Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 16:03:06 +0900 Subject: [PATCH 13/14] modify `on` --- .github/workflows/unit-tests.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index a2c38a7..1fbfab4 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -1,6 +1,13 @@ --- name: Run unit tests -on: push +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize concurrency: group: ${{ github.workflow }}-${{ github.ref }} From cf10d76ec2ca80c4bdade2394a790e2c0e977934 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Sun, 7 Apr 2024 16:10:23 +0900 Subject: [PATCH 14/14] modify workflow file --- .github/workflows/unit-tests.yaml | 33 ++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 1fbfab4..5a4de94 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -5,9 +5,6 @@ on: branches: - main pull_request: - types: - - opened - - synchronize concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -15,18 +12,18 @@ concurrency: jobs: unit-tests: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - - name: Install bazelisk - run: | - bazelisk_dir="$(realpath "$(mktemp -d -p .)")" - wget https://github.com/bazelbuild/bazelisk/releases/download/v1.19.0/bazelisk-linux-amd64 \ - -O "${bazelisk_dir}/bazelisk" - chmod +x "${bazelisk_dir}/bazelisk" - echo "${bazelisk_dir}" >> "${GITHUB_PATH}" - - name: Run unit tests - run: ./execute_tests.sh + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + - name: Install bazelisk + run: | + bazelisk_dir="$(realpath "$(mktemp -d -p .)")" + wget https://github.com/bazelbuild/bazelisk/releases/download/v1.19.0/bazelisk-linux-amd64 \ + -O "${bazelisk_dir}/bazelisk" + chmod +x "${bazelisk_dir}/bazelisk" + echo "${bazelisk_dir}" >> "${GITHUB_PATH}" + - name: Run unit tests + run: ./execute_tests.sh