From 5b676d11c878dd6c9e551b82f9be1f8f9788a782 Mon Sep 17 00:00:00 2001 From: Sebastian Weddmark Olsson Date: Fri, 26 Jun 2026 14:56:13 +0200 Subject: [PATCH] ct: fix -dir and ERL_LIBS for app-provider compiled_suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two regressions in `private/ct.bzl::_impl`, both surfacing only when an `ErlangAppInfo`-providing target (e.g. a `:test_erlang_app` produced by `extract_app(test = True)`) is passed as `compiled_suites` rather than the bare `erlang_bytecode` outputs the `ct_suite` macro produces. ## 1. `-dir test` is bogus when the suite beam is not in `test/` 8faae61 ("Resurrect CI: ... -dir ebin -> -dir test") fixed the `ct_suite` macro path (its `erlang_bytecode("..._beam_files", dest = "test")` puts the suite at `test/.beam`) but hardcoded the arg. When `compiled_suites` is an `extract_app` target, the suite beam post-e70ed5a lives at `/ebin/.beam` — no `test/` exists in the runfiles tree. `ct_run` aborts with Directory .../test is invalid Test run failed! Reason: {make_failed, [...]} Compute `-dir` from the package-relative dirnames of the `_SUITE.beam` entries in `compiled_suites`; fall back to `"test"` when none are found (defensive — `ct_run` would error on an empty `-dir` arg). Drop the now-redundant `-pa ` for any path already covered by `-dir`. ## 2. ERL_LIBS missing the app under test and its prod deps 228b062 originally built ERL_LIBS via `flat_deps(ctx.attr.deps + ctx.attr.compiled_suites)`. 8faae61 narrowed that to `flat_deps(ctx.attr.deps)` because `compiled_suites` under the `ct_suite` macro are `erlang_bytecode` outputs and don't provide `ErlangAppInfo`, so `flat_deps`'s `dep[ErlangAppInfo]` access fails at analysis. That's correct for the macro path, but it stripped the transitive app closure that an `extract_app(test = True)` target contributes. Suites that rely on `application:ensure_all_started/1` of the app under test then fail `init_per_suite` because the app's prod deps are not in ERL_LIBS, e.g. {badmatch,{error,{some_dep,{"no such file or directory" ... Re-include only the `ErlangAppInfo`-providing entries of `compiled_suites` in the `flat_deps` call, leaving plain `.beam` targets alone. ## Validation - `cd test && bazelisk test //...` -> 13/13 PASS - `cd examples/umbrella && bazelisk test //...` -> 9/9 PASS (covers the `ct_suite` macro path via `umbrella_smoke_SUITE`). - Reproduced and fixed against a downstream consumer with `ct_test` targets of the form `ct_test(compiled_suites = [":test_erlang_app"], ...)`: before, every such target failed with the `Directory ... is invalid` error above (or, with #1 alone applied, `init_per_suite` crashed on the missing prod dep); after, they pass. --- private/ct.bzl | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/private/ct.bzl b/private/ct.bzl index 9bc90344..ec748e38 100644 --- a/private/ct.bzl +++ b/private/ct.bzl @@ -18,6 +18,7 @@ load( ) load( ":eunit.bzl", + "invert_package", "package_relative_dirnames", "short_dirname", ) @@ -58,9 +59,21 @@ def sname(ctx): def _impl(ctx): erl_libs_dir = ctx.label.name + "_deps" + # compiled_suites may be raw .beam targets (erlang_bytecode, via the + # ct_suite macro) or a full app target like :test_erlang_app. When + # an entry provides ErlangAppInfo, include its transitive app + # closure in ERL_LIBS — otherwise the suite's runtime deps + # (e.g. application:ensure_all_started/1 of the app under test + # and its prod deps) would not resolve at suite startup. + suite_app_deps = [ + t + for t in ctx.attr.compiled_suites + if ErlangAppInfo in t + ] + erl_libs_files = erl_libs_contents( ctx, - deps = flat_deps(ctx.attr.deps), + deps = flat_deps(ctx.attr.deps + suite_app_deps), ez_deps = ctx.files.ez_deps, dir = erl_libs_dir, ) @@ -69,9 +82,30 @@ def _impl(ctx): erl_libs_path = path_join(package, erl_libs_dir) + # Compute -dir arg(s) from where the actual _SUITE.beam files live. + # The ct_suite macro compiles suites into test/, so for that path + # we'd see ["test"] here; users who pass an extract_app target + # (e.g. :test_erlang_app) directly as compiled_suites see the + # namespaced ebin dir (e.g. test_erlang_app/ebin) post-e70ed5a. + # ct_run hard-fails ("Directory ... is invalid") if any -dir entry + # is missing, so we must point it at real dirs. + suite_dirs = [] + for f in ctx.files.compiled_suites: + if not f.basename.endswith("_SUITE.beam"): + continue + sd = short_dirname(f) + if sd.startswith(package + "/"): + rel = sd.removeprefix(package + "/") + else: + rel = path_join(invert_package(package), sd) + if rel not in suite_dirs: + suite_dirs.append(rel) + if not suite_dirs: + suite_dirs = ["test"] + pa_args = [] for dir in package_relative_dirnames(package, ctx.files.compiled_suites): - if dir != "test": + if dir not in suite_dirs: pa_args.extend(["-pa", dir]) ct_logdir = ctx.attr._ct_logdir[BuildSettingInfo].value @@ -176,7 +210,7 @@ set -x -no_auto_compile \\ -noinput \\ ${{FILTER}} \\ - -dir test {pa_args} \\ + -dir {dir_args} {pa_args} \\ -logdir "{log_dir}" \\ -hidden \\ -sname {sname} ${{COVER_ARGS}} {extra_args} @@ -199,7 +233,8 @@ fi coverdata_to_lcov = coverdata_to_lcov_path, suite_name = ctx.attr.suite_name, pa_args = " ".join(pa_args), - dir = path_join(package, "test"), + dir_args = " ".join(suite_dirs), + dir = path_join(package, suite_dirs[0]), log_dir = log_dir, sname = sname(ctx), extra_args = " ".join(extra_args),