diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf584004a3..f1e3d8e4f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -431,6 +431,12 @@ jobs: # TUs — the single biggest CI cost) via the run_tests_aot dependency and # sweeps all of tests/. Nightly + manual dispatch only; per-PR lanes get # the test_aot_subset compile+link gate from the Build step instead. + # Then the big C++ tests (tests-cpp/big): a generated standalone context + # linked and run, the nano context, concurrent init, the C API split init. + # Their binaries are in the default build every lane makes, but every + # per-PR ctest is -L small, so only this step executes a generated + # context. memory_model_4gb allocates a real 4 GB chunk and stays + # local-only. if: matrix.cmake_preset == 'Release' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') run: | set -eux @@ -451,6 +457,7 @@ jobs: cmake --build ./build --config ${{ matrix.cmake_preset }} --target run_tests_aot ;; esac + ctest --test-dir build --build-config ${{ matrix.cmake_preset }} -L big -E memory_model_4gb --output-on-failure ########################################################### build_windows_relwithdebinfo_nightly: diff --git a/daslib/lint_config.das b/daslib/lint_config.das index ea829fcdbb..03ecdd846c 100644 --- a/daslib/lint_config.das +++ b/daslib/lint_config.das @@ -428,15 +428,15 @@ def public is_lint_fixture_name(base : string) : bool { let public LINT_SKIP_HEADER_LINES = 16 -//! Content-based skip probe shared by the CLI runner and the MCP lint tool: ``expect `` -//! files declare intentional compile errors (dastest), and a header ``// lint-skip-file: `` -//! opts a policy-conflicted file out entirely. Returns the skip reason, or "" to lint the file. -def public lint_file_skip_reason(file : string) : string { +//! Content-based skip probe shared by the CLI runner and the MCP lint tool. An ``expect `` file +//! (intentional compile errors) skips while ``expect_skips`` holds - a caller linting rule fixtures passes false; +//! a header ``// lint-skip-file: `` opts a file out whatever the caller. Returns the skip reason, or "" to lint. +def public lint_file_skip_reason(file : string; expect_skips : bool = true) : string { let text = fread(file) return "" if (empty(text)) for (line, ln in split(text, "\n"), count()) { let trimmed = line |> strip - return "(intentional compile errors via `expect`)" if (trimmed |> starts_with("expect ")) + return "(intentional compile errors via `expect`)" if (expect_skips && trimmed |> starts_with("expect ")) if (ln < LINT_SKIP_HEADER_LINES && trimmed |> starts_with("// lint-skip-file")) { let colon = find(trimmed, ":") let reason = colon >= 0 ? strip(slice(trimmed, colon + 1)) : "" diff --git a/skills/internal/writing_cpp_tests.md b/skills/internal/writing_cpp_tests.md index 160b4014c8..7a02a44c23 100644 --- a/skills/internal/writing_cpp_tests.md +++ b/skills/internal/writing_cpp_tests.md @@ -66,7 +66,7 @@ Big tests that are C++ executables **don't include doctest** - they keep their o **A big test's `int main()` owns its module lifetime** - nothing runs `doctest_main.cpp` for it. An exe that resolves modules through the registry calls `Module::Initialize()` / `Module::Shutdown()` itself. An exe whose only context comes from a generated standalone AOT constructor calls neither: a context that reaches no C++ module beyond the builtin one consults no registry (example: `big/standalone_ctx/test_standalone_ctx.cpp`), and one that does registers what it links itself and shuts it down when the last context is destroyed (example: `examples/standalone/06_full_runtime/`, which doubles as a small-lane test; `big/standalone_ctx/test_standalone_modules.cpp` puts two such contexts in one binary). A test that registered the builtin module - `NEED_ALL_DEFAULT_MODULES` or `NEED_MODULE(Module_BuiltIn)` - owns the registry: it registers every module the context links and calls `Module::Initialize()` before constructing it; a module it missed stops the program at construction, by name (`standalone_modules_host_partial` pins that as a `WILL_FAIL` test). -A big-labelled test is not gated by CI - only the small suite runs there. Run `ninja test-big` locally before pushing one. +A big-labelled test runs in CI only on the nightly and on a manual full-workflow run (`ctest -L big -E memory_model_4gb` on the Release cells); every per-PR ctest is `-L small`. Run `ctest -L big` locally before pushing one and say so in the PR (`tests-cpp/big/REVIEW.md`). ## Doctest assertion cheat sheet diff --git a/tests-cpp/big/REVIEW.md b/tests-cpp/big/REVIEW.md index 856f78b5b0..0b19c220d9 100644 --- a/tests-cpp/big/REVIEW.md +++ b/tests-cpp/big/REVIEW.md @@ -5,5 +5,5 @@ doc: `skills/internal/writing_cpp_tests.md` (repo root). **A diff that touches a test whose ctest labels include `big` says in the PR that the test ran and passed on the author's machine, naming the command - `ctest -L big` or the test's -own binary.** CI runs `ctest -L small` only, so no other run shows that a big-labelled test -passes. +own binary.** Per-PR CI runs `ctest -L small` only; `-L big` runs on the nightly and on a +manual full-workflow run, so a PR's own lanes never show that a big-labelled test passes. diff --git a/tests-cpp/big/style_lint/CMakeLists.txt b/tests-cpp/big/style_lint/CMakeLists.txt index 4f5a31520c..179351621a 100644 --- a/tests-cpp/big/style_lint/CMakeLists.txt +++ b/tests-cpp/big/style_lint/CMakeLists.txt @@ -1,6 +1,7 @@ -# Big test: utils/lint must surface STYLE014/STYLE015 on the canonical -# comment-hygiene fixtures. Catches regressions in the option/flag plumbing -# and the scan_long_comment_blocks pass. +# utils/lint must surface STYLE014/STYLE015 on the canonical comment-hygiene +# fixtures. Catches regressions in the option/flag plumbing and the +# scan_long_comment_blocks pass. Labelled small: each run is one lint of one +# fixture, well under a second, so every per-PR ctest runs it. file(GLOB STYLE_LINT_FIXTURES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/utils/lint/tests/style014_*.das @@ -15,8 +16,8 @@ foreach(_fixture ${STYLE_LINT_FIXTURES}) -- --comment-hygiene true --lint-fixtures true ${_fixture} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) set_tests_properties(style_lint_${_name} PROPERTIES - LABELS "big;style_lint" + LABELS "small;style_lint" PASS_REGULAR_EXPRESSION "STYLE014|STYLE015") endforeach() -add_dependencies(test-big daslang) +add_dependencies(test-small daslang) diff --git a/utils/lint/main.das b/utils/lint/main.das index d84db91cbc..883e0434fc 100644 --- a/utils/lint/main.das +++ b/utils/lint/main.das @@ -417,7 +417,8 @@ def normalize_rule_list(raw : array; var dest : table) : string } def set_skip_reason(file : string; lint_fixtures : bool; var result : LintResult) : bool { - let content_skip = lint_file_skip_reason(file) + // --lint-fixtures lifts the `expect` skip; a lint-skip-file directive always wins + let content_skip = lint_file_skip_reason(file, !lint_fixtures) if (!empty(content_skip)) { result.skip_reason = content_skip return true @@ -425,7 +426,7 @@ def set_skip_reason(file : string; lint_fixtures : bool; var result : LintResult // the lint tool's own rule fixtures violate rules BY DESIGN (warning-expectation // files carry no `expect` directive, so the content skip above misses them). // --lint-fixtures is how this tool's OWN tests ask to see those findings, so it - // lifts both arms; normalize() emits the platform's preferred separator. + // lifts this arm too; normalize() emits the platform's preferred separator. if (!lint_fixtures && (normalize(file) |> replace("\\", "/") |> find("utils/lint/tests/") >= 0 || is_lint_fixture_name(base_name(file)))) { result.skip_reason = "(lint rule fixture)"