diff --git a/README.md b/README.md index 57677d7..46a093b 100644 --- a/README.md +++ b/README.md @@ -82,12 +82,28 @@ auto main() -> int results in the follow build error (snippet): ```console:example/ctest_fail.log -external/skytest/src/detail/test_style.hpp:43:27: error: the value of 'n' is not usable in a constant expression +./src/detail/test_style.hpp:43:27: error: constexpr variable 'value>' must be initialized by a constant expression 43 | static constexpr auto value = std::optional{bool{F{}()}}; - | ^~~~~ -ctest_fail.cpp:9:15: note: 'int n' is not const + | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./src/test.hpp:46:39: note: in instantiation of static data member 'skytest::detail::test_style::compile_time::value' requested here + 46 | result.compile_time = S::template value; + | ^ +./src/test.hpp:71:5: note: (skipping 2 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all) + 71 | assign_impl(static_closure{}); + | ^ +external/llvm_toolchain_llvm/bin/../include/c++/v1/__functional/operations.h:374:37: note: read of non-const variable 'n' is not allowed in a constant expression + 374 | return std::forward<_T1>(__t) < std::forward<_T2>(__u); + | ^ +./src/detail/predicate.hpp:38:24: note: in call to 'static_cast &>(*this).operator()(0, n)' + 38 | const auto value = static_cast(*this)(std::as_const(args)...); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +example/ctest_fail.cpp:10:47: note: (skipping 2 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all) + 10 | "read non-const"_ctest = [] { return expect(lt(0, n)); }; + | ^ +example/ctest_fail.cpp:9:15: note: declared here 9 | static auto n = 0; | ^ +1 error generated. ``` diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index a3c0b3b..fcaf4f7 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -22,11 +22,34 @@ COMMON_CXX_WARNINGS = [ "-Wimplicit-fallthrough", ] +# `@rules_cc` depends on `com_google_protobuf` +http_archive( + name = "com_google_protobuf", + integrity = "sha256-K2lcservjhc/iEI17m1V9XGG6V2J67MTYe5Vy1/RuZY=", + strip_prefix = "protobuf-31.0", + urls = [ + "https://github.com/protocolbuffers/protobuf/archive/v31.0.tar.gz", + ], +) + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +load("@rules_java//java:rules_java_deps.bzl", "rules_java_dependencies") + +rules_java_dependencies() + +load("@rules_java//java:repositories.bzl", "rules_java_toolchains") + +rules_java_toolchains() + +# use a recent version of `@rules_cc` for `@rules_build_error` http_archive( name = "rules_cc", - sha256 = "2037875b9a4456dce4a79d112a8ae885bbc4aad968e6587dca6e64f3a0900cdf", - strip_prefix = "rules_cc-0.0.9", - urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.9/rules_cc-0.0.9.tar.gz"], + sha256 = "712d77868b3152dd618c4d64faaddefcc5965f90f5de6e6dd1d5ddcd0be82d42", + strip_prefix = "rules_cc-0.1.1", + urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.1.1/rules_cc-0.1.1.tar.gz"], ) TOOLCHAINS_LLVM_COMMIT = "4ab573b1b87a57791ef2f9ccee71cbad80a2abb9" @@ -184,12 +207,11 @@ python_register_toolchains( python_version = "3.11", ) -load("@python3_11//:defs.bzl", "interpreter") load("@rules_python//python:pip.bzl", "pip_parse") pip_parse( name = "pip", - python_interpreter_target = interpreter, + python_interpreter_target = "@python3_11_host//:python", requirements_lock = "//tools:requirements.lock", ) @@ -225,3 +247,22 @@ py_library( load("//tools:local_config_info.bzl", "local_config_info") local_config_info(name = "local_config_info") + +RULES_BUILD_ERROR_COMMIT = "4ea1a4fa702b16389c7eb3b695ef97a23dfe9330" + +http_archive( + name = "rules_build_error", + sha256 = "db4363c0346b1e12a58d32e6c9d8b7036fcb0655a2a6391d21a6f13b8bfaed1a", + strip_prefix = "rules_build_error-{commit}".format( + commit = RULES_BUILD_ERROR_COMMIT, + ), + url = "https://github.com/yuyawk/rules_build_error/archive/{commit}.tar.gz".format( + commit = RULES_BUILD_ERROR_COMMIT, + ), +) + +http_archive( + name = "rules_multirun", + sha256 = "0e124567fa85287874eff33a791c3bbdcc5343329a56faa828ef624380d4607c", + url = "https://github.com/keith/rules_multirun/releases/download/0.9.0/rules_multirun.0.9.0.tar.gz", +) diff --git a/example/BUILD.bazel b/example/BUILD.bazel index 80c3e2d..d42bd49 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -1,6 +1,7 @@ +load("@rules_build_error//lang/cc:defs.bzl", "cc_build_error") load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") -load("//rules:sh_binary_template.bzl", "sh_binary_template") load("//rules:binary_log.bzl", "synchronized_binary_log") +load("@rules_multirun//:defs.bzl", "multirun") cc_library( name = "skytest_wrapper", @@ -31,10 +32,15 @@ synchronized_binary_log( src = ":minimal_fail", ) -sh_binary_template( +cc_build_error( name = "ctest_fail", - srcs = ["ctest_fail.sh.tmpl"], - data = ["ctest_fail.cpp"], + src = "ctest_fail.cpp", + copts = [ + "-fno-diagnostics-color", + "-fconstexpr-backtrace-limit=1", + "-ftemplate-backtrace-limit=1", + ], + deps = [":skytest_wrapper"], ) synchronized_binary_log( @@ -131,38 +137,15 @@ cc_binary( deps = [":skytest_wrapper"], ) -update_targets = [ - ":minimal_pass_log.update.sh", - ":minimal_fail_log.update.sh", - ":additional_output_log.update.sh", - ":user_defined_predicates_log.update.sh", - ":described_predicates_log.update.sh", - ":described_predicates_20_log.update.sh", -] - -genrule( - name = "update_all_sh", - srcs = update_targets, - outs = ["update_all.sh"], - cmd = """ -set -euo pipefail -echo "set -euo pipefail" > $@ -echo "" >> $@ -echo "for cmd in {update_targets}; do" >> $@ -echo " \"\\$$cmd\"" >> $@ -echo "done" >> $@ -""".format( - update_targets = " ".join([ - "$(rootpath {})".format(x) - for x in update_targets - ]), - ), - tags = ["manual"], - visibility = ["//visibility:private"], -) - -sh_binary( +multirun( name = "update_all", - srcs = ["update_all.sh"], - data = update_targets, + commands = [ + ":ctest_fail_log.update", + ":minimal_pass_log.update", + ":minimal_fail_log.update", + ":additional_output_log.update", + ":user_defined_predicates_log.update", + ":described_predicates_log.update", + ":described_predicates_20_log.update", + ], ) diff --git a/example/ctest_fail.log b/example/ctest_fail.log index 2583bb7..547b538 100644 --- a/example/ctest_fail.log +++ b/example/ctest_fail.log @@ -1,6 +1,22 @@ -external/skytest/src/detail/test_style.hpp:43:27: error: the value of 'n' is not usable in a constant expression +./src/detail/test_style.hpp:43:27: error: constexpr variable 'value>' must be initialized by a constant expression 43 | static constexpr auto value = std::optional{bool{F{}()}}; - | ^~~~~ -ctest_fail.cpp:9:15: note: 'int n' is not const + | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./src/test.hpp:46:39: note: in instantiation of static data member 'skytest::detail::test_style::compile_time::value' requested here + 46 | result.compile_time = S::template value; + | ^ +./src/test.hpp:71:5: note: (skipping 2 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all) + 71 | assign_impl(static_closure{}); + | ^ +external/llvm_toolchain_llvm/bin/../include/c++/v1/__functional/operations.h:374:37: note: read of non-const variable 'n' is not allowed in a constant expression + 374 | return std::forward<_T1>(__t) < std::forward<_T2>(__u); + | ^ +./src/detail/predicate.hpp:38:24: note: in call to 'static_cast &>(*this).operator()(0, n)' + 38 | const auto value = static_cast(*this)(std::as_const(args)...); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +example/ctest_fail.cpp:10:47: note: (skipping 2 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all) + 10 | "read non-const"_ctest = [] { return expect(lt(0, n)); }; + | ^ +example/ctest_fail.cpp:9:15: note: declared here 9 | static auto n = 0; | ^ +1 error generated. diff --git a/rules/BUILD.bazel b/rules/BUILD.bazel index 7cc2835..2236759 100644 --- a/rules/BUILD.bazel +++ b/rules/BUILD.bazel @@ -1,36 +1,4 @@ -load("@bazel_skylib//rules:expand_template.bzl", "expand_template") -load( - "@local_config_info//:defs.bzl", - "BAZEL_BIN", - "BAZEL_EXTERNAL_DIR", - "BAZEL_WORKSPACE_ROOT", - "XDG_CACHE_HOME", -) - exports_files( - ["skytest_test.sh"], - visibility = ["//test:__pkg__"], -) - -expand_template( - name = "gen_prelude_sh", - out = "prelude.sh", - substitutions = { - "$BAZEL_BIN": BAZEL_BIN, - "$BAZEL_EXTERNAL_DIR": BAZEL_EXTERNAL_DIR, - "$BAZEL_WORKSPACE_ROOT": BAZEL_WORKSPACE_ROOT, - "$XDG_CACHE_HOME": XDG_CACHE_HOME, - }, - tags = ["manual"], - template = "prelude.sh.tmpl", - visibility = ["//visibility:private"], -) - -sh_library( - name = "prelude_sh", - srcs = ["prelude.sh"], - visibility = [ - "//example:__pkg__", - "//test:__pkg__", - ], + ["copy_to_workspace.bash"], + visibility = ["//example:__pkg__"], ) diff --git a/rules/binary_log.bzl b/rules/binary_log.bzl index c6adf78..2fd0828 100644 --- a/rules/binary_log.bzl +++ b/rules/binary_log.bzl @@ -3,39 +3,57 @@ Rule for generating and testing log files for a binary's output """ load("@bazel_skylib//rules:diff_test.bzl", "diff_test") -load("@local_config_info//:defs.bzl", "BAZEL_WORKSPACE_ROOT") +load("@rules_build_error//lang/cc:defs.bzl", "CcBuildErrorInfo") -def binary_log( - name, - src, - log): - """ - Create a log file from running a binary. +def _log_from_build_error(ctx): + output = ctx.actions.declare_file(ctx.attr.log) + input = ctx.attr.src[CcBuildErrorInfo].compile_stderr - Args: - name: string - Name for `binary_log` rule - src: string_label - Source binary file to run - log: string - Filename for created log. - """ - native.genrule( - name = name, - tools = [src], - outs = [log], - cmd = "$(execpath {}) &> $@ || true".format(src), - testonly = True, - tags = ["manual"], - visibility = ["//visibility:private"], - target_compatible_with = select({ - # log files will differ for Clang and GCC due to how types are - # printed. - "@rules_cc//cc/compiler:gcc": ["@platforms//:incompatible"], - "//conditions:default": [], - }), + # remove lines with platform-specific information + # e.g. + # In file included from bazel-out/darwin_arm64-fastbuild/bin/_virtual_includes/skytest/skytest/skytest.hpp:12: + ctx.actions.run_shell( + inputs = [input], + outputs = [output], + command = "grep -v '^In file included from' {input} > {output}".format( + input = input.path, + output = output.path, + ), + progress_message = "Generating log from {}".format(ctx.attr.src.label), ) + return [DefaultInfo(files = depset([output]))] + +def _log_from_binary(ctx): + output = ctx.actions.declare_file(ctx.attr.log) + bin = ctx.attr.src.files.to_list()[0] + + ctx.actions.run_shell( + inputs = [bin], + outputs = [output], + command = "{bin} &> {output} || true".format( + bin = bin.path, + output = output.path, + ), + progress_message = "Generating log from {}".format(ctx.attr.src.label), + ) + + return [DefaultInfo(files = depset([output]))] + +def _binary_log_impl(ctx): + if CcBuildErrorInfo in ctx.attr.src: + return _log_from_build_error(ctx) + return _log_from_binary(ctx) + +binary_log = rule( + implementation = _binary_log_impl, + attrs = { + "src": attr.label(mandatory = True), + "log": attr.string(mandatory = True), + }, + provides = [DefaultInfo], +) + def synchronized_binary_log( name, src, @@ -57,41 +75,36 @@ def synchronized_binary_log( name = name + "_gen", src = src, log = generated_log, - ) - - label = native.package_relative_label(name) - log = log or src_name + ".log" - - native.genrule( - name = name + "_update_sh", - srcs = [log], - tools = [src], - outs = [name + ".update.sh"], - cmd = """ -set -euo pipefail -echo "set -euo pipefail" > $@ -echo "" >> $@ -echo "{workspace_dir}/$(execpath {src}) &> {workspace_dir}/$(rootpath {log}) || true" >> $@ -""".format( - src = src, - log = log, - workspace_dir = BAZEL_WORKSPACE_ROOT, - ), tags = ["manual"], visibility = ["//visibility:private"], + target_compatible_with = select({ + # log files will differ for Clang and GCC due to how types are + # printed. + "@rules_cc//cc/compiler:gcc": ["@platforms//:incompatible"], + "//conditions:default": [], + }), ) + log = log or src_name + ".log" + diff_test( name = name, file1 = log, - file2 = generated_log, + file2 = name + "_gen", failure_message = "To update, run:\n\nbazel run {}.update".format( - str(label).replace("@//", "//"), + str(native.package_relative_label(name)).replace("@//", "//"), ), ) native.sh_binary( name = name + ".update", - srcs = [name + ".update.sh"], - data = [src], + srcs = [Label("copy_to_workspace.bash")], + data = [ + log, + name + "_gen", + ], + args = [ + "$(rootpath :{})".format(name + "_gen"), + "$(rootpath :{})".format(log), + ], ) diff --git a/rules/copy_to_workspace.bash b/rules/copy_to_workspace.bash new file mode 100755 index 0000000..6039d3d --- /dev/null +++ b/rules/copy_to_workspace.bash @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +cp "$1" "$BUILD_WORKSPACE_DIRECTORY/$2" diff --git a/rules/prelude.sh.tmpl b/rules/prelude.sh.tmpl deleted file mode 100644 index 12e7474..0000000 --- a/rules/prelude.sh.tmpl +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -function prelude() { - -TEST_PATH="$(readlink -f $1)" - -PRUNE_TEST="${TEST_PATH##*/test/}}" -PRUNE_EXAMPLE="${PRUNE_TEST##*/example/}}" - -WORKSPACE="${PRUNE_EXAMPLE%.*}" - -cat >> .bazelrc < WORKSPACE.bazel <BUILD.bazel <> $@ testonly = True, ) + deps = kwargs.pop("deps", []) + ["//:skytest"] + copts = kwargs.pop("copts", []) + tests = [] for std in [str(std) for std in cxxstd]: suffix = "." + std - - if binary_type == cc_binary: - binary_kwargs = { - "deps": kwargs.get("deps", []) + ["//:skytest"], - "copts": kwargs.get("copts", []) + ["-std=c++" + std], - "malloc": malloc, - } - elif binary_type == sh_binary_template: - binary_kwargs = { - "substitutions": { - "$CC_BINARY_CXXSTD": std, - "$CC_BINARY_MALLOC": malloc, - }, - } - else: - fail("unhandled binary_type: {}".format(binary_type)) - binary = name + "_bin" + suffix - binary_type( + + cc_binary( name = binary, srcs = srcs, - **(kwargs | binary_kwargs) + deps = deps, + copts = copts + ["-std=c++" + std], + malloc = malloc, + **kwargs ) native.sh_test( diff --git a/test/BUILD.bazel b/test/BUILD.bazel index d80b49c..62b2a30 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -1,5 +1,5 @@ load("//rules:skytest_test.bzl", "skytest_test") -load("//rules:sh_binary_template.bzl", "sh_binary_template") +load("//rules:skytest_compile_error_test.bzl", "skytest_compile_error_test") load("@rules_cc//cc:defs.bzl", "cc_library") skytest_test( @@ -276,15 +276,17 @@ skytest_test( ], ) -skytest_test( +skytest_compile_error_test( name = "noreturn_expect_test", - srcs = ["noreturn_expect.sh.tmpl"], - binary_type = sh_binary_template, - return_code = 1, - stderr = [ - "noreturn_expect.cpp:11:25:.*error:.*ignoring return value of.*expect", - "11 |\\s*expect\\(.*\\).*;", - ], + src = "noreturn_expect.cpp", + stderr = { + "@rules_cc//cc/compiler:gcc": [ + "test/noreturn_expect.cpp:11:25: error: ignoring return value of '.*', declared with attribute 'nodiscard'", + ], + "@rules_cc//cc/compiler:clang": [ + "test/noreturn_expect.cpp:11:9: error: ignoring return value of function declared with 'nodiscard' attribute", + ], + }, ) skytest_test( @@ -297,15 +299,23 @@ skytest_test( ], ) -skytest_test( +skytest_compile_error_test( name = "ctest_build_failure_test", - srcs = ["ctest_build_failure.sh.tmpl"], - binary_type = sh_binary_template, - return_code = 1, - stderr = [ - "11.*test1", - "11.*error.*the value of .*x.* is not usable in a constant expression", - "15.*test2", - "15.*error.*the value of .*x.* is not usable in a constant expression", - ], + src = "ctest_build_failure.cpp", + stderr = { + "@rules_cc//cc/compiler:clang": [ + "12.*\"test1\"_ctest", + "test/ctest_build_failure.cpp:12:23: note: read of non-const variable 'x' is not allowed in a constant expression", + "15.*\"test2\"_ctest", + "test/ctest_build_failure.cpp:16:23: note: read of non-const variable 'x' is not allowed in a constant expression", + ], + "@rules_cc//cc/compiler:gcc": [ + "11.*\"test1\"_ctest", + "test/ctest_build_failure.cpp:11:21: error: the value of 'x' is not usable in a constant expression", + "test/ctest_build_failure.cpp:3:6: note: 'bool x' is not const", + "15.*\"test2\"_ctest", + "test/ctest_build_failure.cpp:15:34: error: the value of 'x' is not usable in a constant expression", + "test/ctest_build_failure.cpp:3:6: note: 'bool x' is not const", + ], + }, ) diff --git a/test/ctest_build_failure.cpp b/test/ctest_build_failure.cpp new file mode 100644 index 0000000..3794ce3 --- /dev/null +++ b/test/ctest_build_failure.cpp @@ -0,0 +1,18 @@ +#include "skytest/skytest.hpp" + +auto x = false; + +auto main() -> int +{ + using namespace ::skytest::literals; + using ::skytest::expect; + using ::skytest::types; + + "test1"_ctest = [] { + return expect(x); + }; + + "test2"_ctest * types = [](auto) { + return expect(x); + }; +} diff --git a/test/ctest_build_failure.sh.tmpl b/test/ctest_build_failure.sh.tmpl deleted file mode 100644 index ba64eea..0000000 --- a/test/ctest_build_failure.sh.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -source rules/prelude.sh -prelude "${BASH_SOURCE[0]}" - -cat >>BUILD.bazel <ctest_build_failure.cpp < int -{ - using namespace ::skytest::literals; - using ::skytest::expect; - using ::skytest::types; - - "test1"_ctest = [] { - return expect(x); - }; - - "test2"_ctest * types = [](auto) { - return expect(x); - }; -} -EOF - -bazel build -s //:ctest_build_failure diff --git a/test/noreturn_expect.cpp b/test/noreturn_expect.cpp new file mode 100644 index 0000000..bd85abb --- /dev/null +++ b/test/noreturn_expect.cpp @@ -0,0 +1,15 @@ +#include "skytest/skytest.hpp" + +auto main() -> int +{ + using namespace ::skytest::literals; + using ::skytest::eq; + using ::skytest::expect; + + "noreturn"_test = [] { + // not returned in test closure + expect(eq(1, 1)); + + return expect(eq(1, 1)); + }; +} diff --git a/test/noreturn_expect.sh.tmpl b/test/noreturn_expect.sh.tmpl deleted file mode 100644 index 2b67e98..0000000 --- a/test/noreturn_expect.sh.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -source rules/prelude.sh -prelude "${BASH_SOURCE[0]}" - -cat >>BUILD.bazel <noreturn_expect.cpp < int -{ - using namespace ::skytest::literals; - using ::skytest::eq; - using ::skytest::expect; - - "noreturn"_test = [] { - // not returned in test closure - expect(eq(1, 1)); - - return expect(eq(1, 1)); - }; -} -EOF - -bazel build -s //:noreturn_expect