diff --git a/CLAUDE.md b/CLAUDE.md index c441085a41..90d65cb85f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -328,7 +328,7 @@ everywhere and reports a by-value out-parameter whose write nobody can read. **Minimize `unsafe`:** most `unsafe(reinterpret)` in macro code exists to strip `const` from raw-pointer field access - fix the root cause by making the function parameter `var`, so field access returns non-const pointers. Reserve `unsafe` for genuinely unsafe operations (pointer arithmetic, `reinterpret` across unrelated types). -**`print` is for user-facing scripts only.** In `tests/`, `daslib/`, `utils/` use `to_log(LOG_INFO|LOG_WARNING|LOG_ERROR)` - same stdout, but level-tagged and filterable. Canonical example: `utils/detect-dupe/main.das`. +**`print` is for user-facing scripts only.** In `tests/`, `daslib/`, `utils/` use `to_log(LOG_INFO|LOG_WARNING|LOG_ERROR)` - level-tagged and filtered by `DAS_LOG_LEVEL` (default `warning`, so `LOG_INFO` lines print only under `DAS_LOG_LEVEL=info`). Canonical example: `utils/detect-dupe/main.das`. **Complexity/length lint (STYLE037/STYLE038): new code meets both limits from the start.** On a hit in existing code, the suppress-vs-split resolution policy is `skills/style_lint.md` - never force a split on an honest shape. diff --git a/ci/test_render_manifests.py b/ci/test_render_manifests.py index 8dd5096a8a..c9733fe8c2 100644 --- a/ci/test_render_manifests.py +++ b/ci/test_render_manifests.py @@ -31,6 +31,21 @@ def fake_sha(asset): return hashlib.sha256(asset.encode()).hexdigest() +def git_bash(): + """The bash the release workflows' `shell: bash` runs - Git's on Windows, where a bare + `bash` from Python resolves to System32's WSL launcher first.""" + if os.name != "nt": + return shutil.which("bash") + git = shutil.which("git") + root = os.path.dirname(git) if git else "" + while root and os.path.dirname(root) != root: + candidate = os.path.join(root, "bin", "bash.exe") + if os.path.isfile(candidate): + return candidate + root = os.path.dirname(root) + return None + + class RenderManifestsTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.mkdtemp() @@ -105,6 +120,7 @@ def test_malformed_sha_is_fatal(self): with self.assertRaises(SystemExit): rm.render_package("daslang", "v0.6.5", self.sha, self.out) + @unittest.skipUnless(git_bash(), "no bash to run checksum_assets.sh") def test_checksum_script_writes_what_the_render_reads(self): here = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packaging") for a in ASSETS["dasllama"]: @@ -112,7 +128,7 @@ def test_checksum_script_writes_what_the_render_reads(self): f.write(a) with open(os.path.join(self.sha, "stale.sha256"), "w") as f: f.write("left alone\n") - subprocess.run(["bash", os.path.join(here, "checksum_assets.sh"), self.sha], check=True, capture_output=True) + subprocess.run([git_bash(), os.path.join(here, "checksum_assets.sh"), self.sha], check=True, capture_output=True) self.assertFalse(os.path.exists(os.path.join(self.sha, "stale.sha256.sha256")), "a .sha256 is never summed") rm.render_package("dasllama", "v0.6.5", self.sha, self.out) bucket = json.loads(self.read("scoop-daslang/bucket/dasllama.json")) diff --git a/modules/dasHV/CMakeLists.txt b/modules/dasHV/CMakeLists.txt index 1cedd6e1d0..66ef2f245e 100644 --- a/modules/dasHV/CMakeLists.txt +++ b/modules/dasHV/CMakeLists.txt @@ -179,12 +179,14 @@ IF ((NOT DAS_HV_INCLUDED) AND ((NOT ${DAS_HV_DISABLED}) OR (NOT DEFINED DAS_HV_D URL https://github.com/ithewei/libhv/archive/5da5fc22cdfd1b59ba2b0520abb34f95d050975c.tar.gz URL_HASH SHA256=b4f774936ccd6981df74b23f9e033e0b3ab584f6682d889490377dc9450b5f54 DOWNLOAD_EXTRACT_TIMESTAMP TRUE + PATCH_COMMAND ${CMAKE_COMMAND} -DLIBHV_SRC_DIR= -P ${DAS_HV_DIR}/patch_libhv.cmake PREFIX ${CMAKE_CURRENT_BINARY_DIR}/libhv CMAKE_ARGS ${HV_CMAKE_FLAGS} BUILD_BYPRODUCTS ${HV_LIBRARIES} BUILD_COMMAND ${CMAKE_COMMAND} --build --config $ INSTALL_COMMAND ${CMAKE_COMMAND} --install --config $ --prefix ${DAS_HV_DIR}/hv/$ ) + ExternalProject_Add_StepDependencies(LIBHV patch ${DAS_HV_DIR}/patch_libhv.cmake) # Copy cert files from downloaded libhv source (not stored in repo to avoid pushing private keys) SET(LIBHV_SRC_DIR ${CMAKE_CURRENT_BINARY_DIR}/libhv/src/LIBHV) ExternalProject_Add_Step(LIBHV copy_certs diff --git a/modules/dasHV/patch_libhv.cmake b/modules/dasHV/patch_libhv.cmake new file mode 100644 index 0000000000..773e9413ad --- /dev/null +++ b/modules/dasHV/patch_libhv.cmake @@ -0,0 +1,85 @@ +# Apply the libhv fixes this tree carries on top of the pinned tarball; each one is dropped when the +# pin moves past its upstream merge. The first run saves each file as .das_pristine, and every +# run patches from that copy - so re-running, or editing a hunk here, rebuilds the file from the +# tarball's text. A pristine anchor that is missing fails the build: a pin bump that touched the +# patched code re-decides the patch, it never builds unpatched. +# +# base/hlog.c - logger_set_file closes the open log file, so a later hlog_set_file switches files +# instead of writing to the old one until the date changes (ithewei/libhv#892). +# +# Usage: cmake -DLIBHV_SRC_DIR= -P patch_libhv.cmake + +if(NOT LIBHV_SRC_DIR) + message(FATAL_ERROR "patch_libhv.cmake: pass -DLIBHV_SRC_DIR=") +endif() + +function(das_hv_patch_begin file) + set(path "${LIBHV_SRC_DIR}/${file}") + if(NOT EXISTS "${path}.das_pristine") + configure_file("${path}" "${path}.das_pristine" COPYONLY) + endif() + file(READ "${path}.das_pristine" src) + set(das_hv_file "${file}" PARENT_SCOPE) + set(das_hv_src "${src}" PARENT_SCOPE) +endfunction() + +function(das_hv_hunk pristine patched) + string(FIND "${das_hv_src}" "${pristine}" at) + if(at EQUAL -1) + string(REGEX MATCH "^[^\n]*" first_line "${pristine}") + string(STRIP "${first_line}" first_line) + message(FATAL_ERROR "patch_libhv.cmake: ${das_hv_file} no longer carries the pristine anchor " + "'${first_line}' - the libhv pin changed this code; re-decide the patch in modules/dasHV/patch_libhv.cmake") + endif() + string(REPLACE "${pristine}" "${patched}" src "${das_hv_src}") + set(das_hv_src "${src}" PARENT_SCOPE) +endfunction() + +function(das_hv_patch_end) + file(WRITE "${LIBHV_SRC_DIR}/${das_hv_file}" "${das_hv_src}") + message(STATUS "patch_libhv.cmake: patched ${das_hv_file}") +endfunction() + +das_hv_patch_begin("base/hlog.c") +das_hv_hunk([=[ + logger_set_file(logger, DEFAULT_LOG_FILE); + logger->last_logfile_ts = 0; + logger->can_write_cnt = -1; + hmutex_init(&logger->mutex_); +]=] [=[ + logger->last_logfile_ts = 0; + logger->can_write_cnt = -1; + hmutex_init(&logger->mutex_); + logger_set_file(logger, DEFAULT_LOG_FILE); +]=]) +das_hv_hunk([=[ +void logger_set_file(logger_t* logger, const char* filepath) { + strncpy(logger->filepath, filepath, sizeof(logger->filepath) - 1); + // remove suffix .log + char* suffix = strrchr(logger->filepath, '.'); + if (suffix && strcmp(suffix, ".log") == 0) { + *suffix = '\0'; + } +} +]=] [=[ +void logger_set_file(logger_t* logger, const char* filepath) { + hmutex_lock(&logger->mutex_); + strncpy(logger->filepath, filepath, sizeof(logger->filepath) - 1); + logger->filepath[sizeof(logger->filepath) - 1] = '\0'; + // remove suffix .log + char* suffix = strrchr(logger->filepath, '.'); + if (suffix && strcmp(suffix, ".log") == 0) { + *suffix = '\0'; + } + // close the current logfile, the next write opens the new one + if (logger->fp_) { + fclose(logger->fp_); + logger->fp_ = NULL; + } + logger->cur_logfile[0] = '\0'; + logger->last_logfile_ts = 0; + logger->can_write_cnt = -1; + hmutex_unlock(&logger->mutex_); +} +]=]) +das_hv_patch_end() diff --git a/modules/dasLLAMA/dasllama/dasllama_fat_start.das b/modules/dasLLAMA/dasllama/dasllama_fat_start.das index bdae005d2e..7abebcb65e 100644 --- a/modules/dasLLAMA/dasllama/dasllama_fat_start.das +++ b/modules/dasLLAMA/dasllama/dasllama_fat_start.das @@ -6,7 +6,7 @@ module dasllama_fat_start shared private require dasllama/dasllama_lint public require dasllama/dasllama_common -require dasllama/dasllama_env // dasllama_home_dir - where a fat exe keeps its sidecar +require dasllama/dasllama_env // nolint:STYLE030 - dasllama_home_dir, used only inside the llvm_tune static_if half require daslib/fio require dasllama/dasllama_math // the runtime knob getters the snapshot records require dasllama/dasllama_par // get_total_hw_jobs - the advisory "threads" entry diff --git a/skills/internal/writing_cpp_tests.md b/skills/internal/writing_cpp_tests.md index 7a02a44c23..eaef32d560 100644 --- a/skills/internal/writing_cpp_tests.md +++ b/skills/internal/writing_cpp_tests.md @@ -44,7 +44,7 @@ Rebuild - `cmake --build build`. The new test appears in `ctest -N` automaticall ## Adding a big test -A "big" test is anything the single doctest exe can't host: a custom `int main()` (C++ or C), custom link deps, multiple link variants (static + dynamic), or no compiled source at all - `big/style_lint/` is just a `CMakeLists.txt` that registers ctest entries running a daslang script with a `PASS_REGULAR_EXPRESSION`. What makes it "big" is owning its own CMakeLists. The ctest LABEL, not the folder, decides which lane runs it: the default is `LABELS "big"` with `add_dependencies(test-big )`. A dir that must run on every PR carries `LABELS "small"` with `add_dependencies(test-small )` instead, so the local small run builds it. Ledgered small-labelled dir: `big/vecmath_backend/` - its scalar arm compiles vecmath with a different `vec4f` than libDaScript was built with, so it cannot link libDaScript and cannot join the small glob exe. The canonical C++ example is `tests-cpp/big/concurrent_init/` - it has a thread-startup-barrier harness and is built as both `test_concurrent_init` (static) and `test_concurrent_init_dyn` (dynamic). +A "big" test is anything the single doctest exe can't host: a custom `int main()` (C++ or C), custom link deps, multiple link variants (static + dynamic), or no compiled source at all - `big/style_lint/` is just a `CMakeLists.txt` that registers ctest entries running a daslang script with a `PASS_REGULAR_EXPRESSION`. What makes it "big" is owning its own CMakeLists. The ctest LABEL, not the folder, decides which lane runs it: the default is `LABELS "big"` with `add_dependencies(test-big )`. A dir that must run on every PR carries `LABELS "small"` with `add_dependencies(test-small )` instead, so the local small run builds it; a dir whose tests only run `${CMAKE_COMMAND}` or `${CMAKE_CTEST_COMMAND}` and that declares no target builds nothing and wires nothing (`big/dashv_patch_libhv/`). Ledgered small-labelled dir: `big/vecmath_backend/` - its scalar arm compiles vecmath with a different `vec4f` than libDaScript was built with, so it cannot link libDaScript and cannot join the small glob exe. The canonical C++ example is `tests-cpp/big/concurrent_init/` - it has a thread-startup-barrier harness and is built as both `test_concurrent_init` (static) and `test_concurrent_init_dyn` (dynamic). To add one: diff --git a/tests-cpp/REVIEW.das b/tests-cpp/REVIEW.das index 472b03f816..3611698cf8 100644 --- a/tests-cpp/REVIEW.das +++ b/tests-cpp/REVIEW.das @@ -76,7 +76,22 @@ def private check_source_property_paths(f : string; text : string) { } } -// Every lane a file labels tests into is a lane that file also builds for. +//! true only when every test runs cmake or ctest itself and the file declares no target or custom command - nothing a lane could leave stale +def private builds_nothing_for_its_tests(text : string) : bool { + var inscope commands <- cmake_test_commands(text) + var inscope tests <- cmake_command_blocks(text, "add_test") + return false if (empty(commands) || length(commands) != length(tests)) + for (c in commands) { + return false if (c != "$\{CMAKE_COMMAND\}" && c != "$\{CMAKE_CTEST_COMMAND\}") + } + for (kind in ["add_executable", "add_library", "add_custom_target", "add_custom_command"]) { + var inscope declared <- cmake_command_blocks(text, kind) + return false if (!empty(declared)) + } + return true +} + +// A file whose tests run something it builds wires every lane it labels them into. def private check_lane_wiring { let top_cmake = "tests-cpp/CMakeLists.txt" var inscope aggregate_lanes <- lanes_of(fread(top_cmake), "add_custom_target") @@ -88,10 +103,12 @@ def private check_lane_wiring { // a file that defines an aggregate wires its dependencies at the definition site wired_lanes |> push_from(lanes_of(text, "add_custom_target")) var inscope labels <- cmake_test_labels(text) - for (lab in labels) { - // a label with no test- target is a ctest filter — nothing to wire to - continue if (find_index(aggregate_lanes, lab) < 0 || find_index(wired_lanes, lab) >= 0) - gate_finding(f, "labels a test \"{lab}\" but wires nothing into test-{lab} — wire it with add_dependencies(test-{lab} ), or the lane runs a binary it never built") + if (!builds_nothing_for_its_tests(text)) { + for (lab in labels) { + // a label with no test- target is a ctest filter — nothing to wire to + continue if (find_index(aggregate_lanes, lab) < 0 || find_index(wired_lanes, lab) >= 0) + gate_finding(f, "labels a test \"{lab}\" but wires nothing into test-{lab} — wire it with add_dependencies(test-{lab} ), or the lane runs a binary it never built") + } } check_test_targets(f, text) check_source_property_paths(f, text) diff --git a/tests-cpp/REVIEW.md b/tests-cpp/REVIEW.md index ab3bef0154..734eff1ee3 100644 --- a/tests-cpp/REVIEW.md +++ b/tests-cpp/REVIEW.md @@ -13,3 +13,10 @@ subfolder's checklist as well as this one.** builds, registers, or runs - a doctest skip, a `#if` a configuration leaves undefined, a CMake gate on a build option - names in the PR one configuration that does run that test: its configure flags and its ctest command.** + +**A diff to `check_lane_wiring` or `builds_nothing_for_its_tests` in `REVIEW.das` keeps the gate +reporting every `CMakeLists.txt` under this folder that labels a test into a `test-` target +the top `CMakeLists.txt` defines, neither defines that target nor names it in +`add_dependencies(test- ...)`, and builds something for its tests - a test whose command is +not `${CMAKE_COMMAND}` or `${CMAKE_CTEST_COMMAND}`, or an `add_executable`, `add_library`, +`add_custom_target` or `add_custom_command`.** diff --git a/tests-cpp/big/dashv_patch_libhv/CMakeLists.txt b/tests-cpp/big/dashv_patch_libhv/CMakeLists.txt new file mode 100644 index 0000000000..3ba9ace07d --- /dev/null +++ b/tests-cpp/big/dashv_patch_libhv/CMakeLists.txt @@ -0,0 +1,11 @@ +# modules/dasHV/patch_libhv.cmake over a fixture of the two hlog.c passages it anchors on. Labelled +# small: one cmake -P over a two-passage fixture, so every per-PR ctest runs it. + +add_test( + NAME dashv_patch_libhv + COMMAND ${CMAKE_COMMAND} + -DPATCH_SCRIPT=${PROJECT_SOURCE_DIR}/modules/dasHV/patch_libhv.cmake + -DFIXTURE=${CMAKE_CURRENT_SOURCE_DIR}/pristine_hlog.c + -DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/work + -P ${CMAKE_CURRENT_SOURCE_DIR}/test_patch_libhv.cmake) +set_tests_properties(dashv_patch_libhv PROPERTIES LABELS "small") diff --git a/tests-cpp/big/dashv_patch_libhv/pristine_hlog.c b/tests-cpp/big/dashv_patch_libhv/pristine_hlog.c new file mode 100644 index 0000000000..050e1f23d5 --- /dev/null +++ b/tests-cpp/big/dashv_patch_libhv/pristine_hlog.c @@ -0,0 +1,16 @@ +static void logger_init(logger_t* logger) { + logger->enable_fsync = 1; + logger_set_file(logger, DEFAULT_LOG_FILE); + logger->last_logfile_ts = 0; + logger->can_write_cnt = -1; + hmutex_init(&logger->mutex_); +} + +void logger_set_file(logger_t* logger, const char* filepath) { + strncpy(logger->filepath, filepath, sizeof(logger->filepath) - 1); + // remove suffix .log + char* suffix = strrchr(logger->filepath, '.'); + if (suffix && strcmp(suffix, ".log") == 0) { + *suffix = '\0'; + } +} diff --git a/tests-cpp/big/dashv_patch_libhv/test_patch_libhv.cmake b/tests-cpp/big/dashv_patch_libhv/test_patch_libhv.cmake new file mode 100644 index 0000000000..07d50de386 --- /dev/null +++ b/tests-cpp/big/dashv_patch_libhv/test_patch_libhv.cmake @@ -0,0 +1,78 @@ +# Drives modules/dasHV/patch_libhv.cmake over pristine_hlog.c: a fresh run patches and saves the +# pristine copy, a re-run rebuilds from that copy, a drifted anchor and a missing source dir fail. +# +# Usage: cmake -DPATCH_SCRIPT= -DFIXTURE= -DWORK_DIR= -P test_patch_libhv.cmake + +function(stage case_dir fixture_text) + file(REMOVE_RECURSE "${case_dir}") + file(WRITE "${case_dir}/base/hlog.c" "${fixture_text}") +endfunction() + +function(run_patch src_dir out_rc out_log) + if(src_dir STREQUAL "") + execute_process(COMMAND ${CMAKE_COMMAND} -P ${PATCH_SCRIPT} + RESULT_VARIABLE rc OUTPUT_VARIABLE out ERROR_VARIABLE err) + else() + execute_process(COMMAND ${CMAKE_COMMAND} -DLIBHV_SRC_DIR=${src_dir} -P ${PATCH_SCRIPT} + RESULT_VARIABLE rc OUTPUT_VARIABLE out ERROR_VARIABLE err) + endif() + set(${out_rc} "${rc}" PARENT_SCOPE) + set(${out_log} "${out}${err}" PARENT_SCOPE) +endfunction() + +function(fail message) + message(FATAL_ERROR "test_patch_libhv: ${message}") +endfunction() + +file(READ "${FIXTURE}" pristine) + +set(fresh "${WORK_DIR}/fresh") +stage("${fresh}" "${pristine}") +run_patch("${fresh}" rc log) +if(NOT rc EQUAL 0) + fail("a fresh run fails: ${log}") +endif() +file(READ "${fresh}/base/hlog.c" patched) +file(READ "${fresh}/base/hlog.c.das_pristine" saved) +if(NOT saved STREQUAL pristine) + fail("the saved copy is not the file the run found") +endif() +string(FIND "${patched}" "hmutex_init(&logger->mutex_);\n logger_set_file(logger, DEFAULT_LOG_FILE);" at_init) +if(at_init EQUAL -1) + fail("logger_init does not initialize the mutex before logger_set_file") +endif() +string(FIND "${patched}" "fclose(logger->fp_);" at_close) +if(at_close EQUAL -1) + fail("logger_set_file does not close the open log file") +endif() + +run_patch("${fresh}" rc log) +file(READ "${fresh}/base/hlog.c" rerun) +if(NOT rc EQUAL 0 OR NOT rerun STREQUAL patched) + fail("a re-run fails or changes the patched file: ${log}") +endif() + +file(WRITE "${fresh}/base/hlog.c" "a file an older patch left behind\n") +run_patch("${fresh}" rc log) +file(READ "${fresh}/base/hlog.c" rebuilt) +if(NOT rc EQUAL 0 OR NOT rebuilt STREQUAL patched) + fail("a re-run does not rebuild from the saved pristine copy: ${log}") +endif() + +set(drift "${WORK_DIR}/drift") +string(REPLACE "hmutex_init(&logger->mutex_);" "hmutex_init(&logger->lock_);" drifted "${pristine}") +stage("${drift}" "${drifted}") +run_patch("${drift}" rc log) +string(FIND "${log}" "no longer carries the pristine anchor" at_msg) +if(rc EQUAL 0 OR at_msg EQUAL -1) + fail("a drifted anchor builds, or fails without naming it: ${log}") +endif() + +run_patch("" rc log) +string(FIND "${log}" "-DLIBHV_SRC_DIR" at_usage) +if(rc EQUAL 0 OR at_usage EQUAL -1) + fail("a run with no source dir succeeds, or fails without its usage: ${log}") +endif() + +file(REMOVE_RECURSE "${WORK_DIR}") +message(STATUS "test_patch_libhv: all cases passed") diff --git a/tests-cpp/small/test_seqbox_concurrent_writers.cpp b/tests-cpp/small/test_seqbox_concurrent_writers.cpp index 58d1003afd..b4552e21fc 100644 --- a/tests-cpp/small/test_seqbox_concurrent_writers.cpp +++ b/tests-cpp/small/test_seqbox_concurrent_writers.cpp @@ -66,8 +66,13 @@ TEST_CASE("seq box survives concurrent writers") { } }); + const auto landingDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); start.store(true, std::memory_order_release); std::this_thread::sleep_for(std::chrono::milliseconds(250)); + while ((published.load(std::memory_order_relaxed) == 0 || reads.load(std::memory_order_relaxed) == 0) + && std::chrono::steady_clock::now() < landingDeadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } stop.store(true, std::memory_order_relaxed); publisherA.join(); publisherB.join(); clearer.join(); reader.join(); diff --git a/utils/common/log_floor.das b/utils/common/log_floor.das new file mode 100644 index 0000000000..af654242bd --- /dev/null +++ b/utils/common/log_floor.das @@ -0,0 +1,12 @@ +options gen2 + +module log_floor + +require daslib/fio + +def public default_log_floor_to_info() { + //! to_log drops records below DAS_LOG_LEVEL (default warning); an unset floor becomes info, for this process and the programs it starts + if (empty(get_env_variable("DAS_LOG_LEVEL"))) { + set_env_variable("DAS_LOG_LEVEL", "info") + } +} diff --git a/utils/internal/make-pr/main.das b/utils/internal/make-pr/main.das index 7a28c603e6..9200e5738b 100644 --- a/utils/internal/make-pr/main.das +++ b/utils/internal/make-pr/main.das @@ -12,6 +12,7 @@ require daslib/defer require daslib/strings_boost require daslib/json_boost require gates +require ../../common/log_floor.das [CommandLineArgs] struct Config { @@ -293,6 +294,7 @@ def private want(gate : string) : bool { [export] def main() : int { + default_log_floor_to_info() let rc = parse_args_with_help(g_cfg, "make-pr") return rc if (rc >= 0) let bad <- unknown_gate_names(g_cfg.only, g_cfg.skip) @@ -321,7 +323,7 @@ def main() : int { var out = "" let code = run([daslang, "utils/internal/preflight/main.das", "--", "--full"], out, 7200.0) if (code != 0) { - to_log(LOG_ERROR, out) //! the run's own gate lines, at the level the default log floor lets through + to_log(LOG_ERROR, out) to_log(LOG_ERROR, "preflight --full failed - fix each, validate with the TARGETED gate, never a second full run\n") return 2 } diff --git a/utils/internal/make-pr/test_gates.das b/utils/internal/make-pr/test_gates.das index 1d112b4f0d..96ea3ec98b 100644 --- a/utils/internal/make-pr/test_gates.das +++ b/utils/internal/make-pr/test_gates.das @@ -2,7 +2,34 @@ options gen2 require dastest/testing_boost public require daslib/fio +require daslib/command_line +require strings require gates +require ../../common/log_floor.das + +[test] +def test_default_log_floor_to_info(t : T?) { + let saved = get_env_variable("DAS_LOG_LEVEL") + set_env_variable("DAS_LOG_LEVEL", "") + default_log_floor_to_info() + t |> success(get_env_variable("DAS_LOG_LEVEL") == "info", "an unset floor reads at info") + set_env_variable("DAS_LOG_LEVEL", "error") + default_log_floor_to_info() + t |> success(get_env_variable("DAS_LOG_LEVEL") == "error", "a floor the caller set stands") + set_env_variable("DAS_LOG_LEVEL", saved) +} + +[test] +def test_main_prints_its_gate_lines_with_no_floor_set(t : T?) { + let no_floor_prefix = get_platform_name() == "windows" ? "set \"DAS_LOG_LEVEL=\"&& " : "env -u DAS_LOG_LEVEL " + var out : string + unsafe(popen_timeout("{no_floor_prefix}{get_das_exe()} utils/internal/make-pr/main.das -- --only stamp-reach", 120.0) $(f) { + if (f != null) { + out = fread(f) + } + }) + t |> success(find(out, "stamp-reach:") >= 0, "make-pr's gate lines print with DAS_LOG_LEVEL unset:\n{out}") +} [test] def test_want_gate(t : T?) { diff --git a/utils/internal/preflight/main.das b/utils/internal/preflight/main.das index 92eaf12cd4..2c9e90a5b6 100644 --- a/utils/internal/preflight/main.das +++ b/utils/internal/preflight/main.das @@ -26,6 +26,7 @@ require strings require math require ../../common/parallel_workers.das require ../../common/git_signature.das +require ../../common/log_floor.das require ../../../dastest/worker_policy.das require config require compile_db @@ -1727,10 +1728,7 @@ def report_gate(r : GateResult; verbose : bool) { [export] def main() : int { // nolint:STYLE037,STYLE038 — the gate loop + CLI surface; the phases ARE the readability - //! the gate ledger is preflight's output, not a trace: the run reads at info unless the caller set a floor - if (empty(get_env_variable("DAS_LOG_LEVEL"))) { - set_env_variable("DAS_LOG_LEVEL", "info") - } + default_log_floor_to_info() var args_r <- parse_args(type) if (args_r |> is_err) { to_log(LOG_ERROR, "error: {args_r |> unwrap_err}\n\n")