From 410b516071336828e625742515dac25a3a19e453 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Thu, 7 May 2026 10:34:43 +0000 Subject: [PATCH 1/5] Add bisect_ppx coverage scaffolding Wires bisect_ppx instrumentation through every OCaml library and executable in the workspace (compiler, analysis, tools, ounit tests) and adds make targets for producing reports. - `make coverage` builds an instrumented toolchain, runs the full test suite via `node scripts/test.js -all`, and emits HTML + summary reports under `_coverage/`. - `make coverage-super-errors` is a focused flow: it runs only the super_errors fixtures and prints per-file coverage for the type checker / error reporting modules so it's easy to spot which error paths the snapshot fixtures don't exercise. - Frozen (`parsetree0.ml`) and cppo-generated `compiler/ext` variants are filtered from reports via `--ignore-files` patterns. - bisect_ppx is added as a `with-dev-setup` dep in `rescript.opam.template`, so default test builds are unaffected. --- .gitignore | 2 + Makefile | 100 ++++++++++++++++++++++++++++++++++++++- analysis/bin/dune | 2 + analysis/src/dune | 2 + compiler/bsc/dune | 2 + compiler/common/dune | 2 + compiler/core/dune | 2 + compiler/depends/dune | 2 + compiler/ext/dune | 2 + compiler/frontend/dune | 2 + compiler/gentype/dune | 2 + compiler/ml/dune | 2 + compiler/syntax/cli/dune | 2 + compiler/syntax/src/dune | 2 + rescript.opam.template | 1 + tests/ounit_tests/dune | 2 + tools/bin/dune | 2 + tools/src/dune | 2 + 18 files changed, 132 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d31b5d90603..0b5c90556f6 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ _build_playground node_modules *.dump coverage +_coverage/ +bisect*.coverage lib/ocaml tests/build_tests/*/lib/ diff --git a/Makefile b/Makefile index 088d3d60003..5ccd92a6484 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,104 @@ format: | $(YARN_INSTALL_STAMP) checkformat: | $(YARN_INSTALL_STAMP) ./scripts/format_check.sh +# Coverage (bisect_ppx) +# +# Requires the `bisect_ppx` opam package (>= 2.8.0) in your switch: +# opam install bisect_ppx +# or pull it in via the rescript dev-setup deps: +# opam install . --deps-only --with-dev-setup +# +# Quick start: +# make coverage-super-errors # focused report for super-errors fixtures +# make coverage # full report across all test suites +# make clean-coverage # remove all coverage artifacts + +COVERAGE_DIR := _coverage +COVERAGE_FILES_DIR := $(COVERAGE_DIR)/files +COVERAGE_HTML_DIR := $(COVERAGE_DIR)/html +COVERAGE_BISECT_PREFIX := $(abspath $(COVERAGE_FILES_DIR))/bisect + +# Patterns omitted from reports (frozen / vendored / cppo-generated) +COVERAGE_IGNORE := \ + --ignore-missing-files \ + --ignore-files 'compiler/ml/parsetree0\.ml' \ + --ignore-files 'compiler/ext/.*_(string|int|ident|poly|local_ident)\.ml$$' \ + --ignore-files 'compiler/ext/(hash|hash_set|map|set|vec|ordered_hash_map)\.ml$$' + +# Re-builds the toolchain with bisect_ppx instrumentation and swaps the +# instrumented binaries into BIN_DIR so any test runner that shells out to +# `bsc` produces .coverage files. Skips `strip` to keep debug info intact. +.PHONY: coverage-build +coverage-build: | $(YARN_INSTALL_STAMP) + dune build --instrument-with bisect_ppx + @$(foreach bin,$(COMPILER_DUNE_BINS),touch $(bin);) + @$(foreach bin,$(COMPILER_BIN_NAMES), \ + cp $(DUNE_BIN_DIR)/$(bin)$(PLATFORM_EXE_EXT) $(BIN_DIR)/$(bin).exe && \ + chmod 755 $(BIN_DIR)/$(bin).exe;) + +.PHONY: coverage-prepare +coverage-prepare: clean-coverage coverage-build + mkdir -p $(COVERAGE_FILES_DIR) + +# Build the runtime with the instrumented bsc so subsequent test runs have +# a fresh stdlib. Coverage from the runtime build is discarded so reports +# only reflect what the tests exercised. +.PHONY: coverage-lib +coverage-lib: coverage-prepare + BISECT_FILE=$(COVERAGE_BISECT_PREFIX)-discard BISECT_SILENT=YES \ + yarn workspace @rescript/runtime build + rm -f $(COVERAGE_BISECT_PREFIX)-discard*.coverage + +.PHONY: coverage-run +coverage-run: coverage-lib + BISECT_FILE=$(COVERAGE_BISECT_PREFIX) BISECT_SILENT=YES \ + node scripts/test.js -all + +.PHONY: coverage-run-super-errors +coverage-run-super-errors: coverage-prepare + BISECT_FILE=$(COVERAGE_BISECT_PREFIX) BISECT_SILENT=YES \ + node tests/build_tests/super_errors/input.js + +.PHONY: coverage-report +coverage-report: + bisect-ppx-report html \ + --coverage-path $(COVERAGE_FILES_DIR) \ + $(COVERAGE_IGNORE) \ + -o $(COVERAGE_HTML_DIR) + bisect-ppx-report summary \ + --coverage-path $(COVERAGE_FILES_DIR) \ + $(COVERAGE_IGNORE) + @echo "" + @echo "HTML report: $(COVERAGE_HTML_DIR)/index.html" + +.PHONY: coverage-report-cobertura +coverage-report-cobertura: + bisect-ppx-report cobertura \ + --coverage-path $(COVERAGE_FILES_DIR) \ + $(COVERAGE_IGNORE) \ + -o $(COVERAGE_DIR)/cobertura.xml + +.PHONY: coverage +coverage: coverage-run coverage-report + +# Focused report for the super-errors fixtures. Prints per-file coverage for +# the type-checker / error-reporting modules so it's easy to spot which error +# paths are unexercised. +.PHONY: coverage-super-errors +coverage-super-errors: coverage-run-super-errors coverage-report + @echo "" + @echo "Per-file coverage for error-reporting modules:" + @bisect-ppx-report summary --per-file \ + --coverage-path $(COVERAGE_FILES_DIR) \ + $(COVERAGE_IGNORE) \ + | grep -E "(error_message|typecore|typetexp|typedecl|typemod|matching|location|includemod|parmatch|ast_untagged_variants)" \ + || true + +.PHONY: clean-coverage +clean-coverage: + rm -rf $(COVERAGE_DIR) + find . -name 'bisect*.coverage' -not -path './_build/*' -delete + # Clean clean-gentype: @@ -225,7 +323,7 @@ clean-gentype: clean-tests: clean-gentype -clean: clean-lib clean-compiler clean-rewatch +clean: clean-lib clean-compiler clean-rewatch clean-coverage dev-container: docker build -t rescript-dev-container docker diff --git a/analysis/bin/dune b/analysis/bin/dune index 64c2a78156e..c7be0fc681b 100644 --- a/analysis/bin/dune +++ b/analysis/bin/dune @@ -8,4 +8,6 @@ (package analysis) (modes byte exe) (name main) + (instrumentation + (backend bisect_ppx)) (libraries analysis)) diff --git a/analysis/src/dune b/analysis/src/dune index c1bd828c114..0b27f5aac8d 100644 --- a/analysis/src/dune +++ b/analysis/src/dune @@ -1,5 +1,7 @@ (library (name analysis) + (instrumentation + (backend bisect_ppx)) (flags (-w "+6+26+27+32+33+39")) (libraries unix str ext ml jsonlib syntax reanalyze)) diff --git a/compiler/bsc/dune b/compiler/bsc/dune index 4a50387af2d..97226e1fa77 100644 --- a/compiler/bsc/dune +++ b/compiler/bsc/dune @@ -7,6 +7,8 @@ (name rescript_compiler_main) (public_name bsc) (package rescript) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-9-30-40-41-42-48-70)) (libraries common core depends flow_parser gentype syntax)) diff --git a/compiler/common/dune b/compiler/common/dune index b6962b3d215..1b09233e54d 100644 --- a/compiler/common/dune +++ b/compiler/common/dune @@ -1,6 +1,8 @@ (library (name common) (wrapped false) + (instrumentation + (backend bisect_ppx)) (preprocess (action (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) diff --git a/compiler/core/dune b/compiler/core/dune index d7d75f34437..f1fd51ab419 100644 --- a/compiler/core/dune +++ b/compiler/core/dune @@ -1,6 +1,8 @@ (library (name core) (wrapped false) + (instrumentation + (backend bisect_ppx)) (preprocess (action (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) diff --git a/compiler/depends/dune b/compiler/depends/dune index be76caa4081..1954403aabe 100644 --- a/compiler/depends/dune +++ b/compiler/depends/dune @@ -1,6 +1,8 @@ (library (name depends) (wrapped false) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-40-42)) (libraries common)) diff --git a/compiler/ext/dune b/compiler/ext/dune index 1e2117bcce8..868fd7dddfe 100644 --- a/compiler/ext/dune +++ b/compiler/ext/dune @@ -1,6 +1,8 @@ (library (name ext) (wrapped false) + (instrumentation + (backend bisect_ppx)) (preprocess (action (run diff --git a/compiler/frontend/dune b/compiler/frontend/dune index d4a7e7dfc74..c94fbd55e93 100644 --- a/compiler/frontend/dune +++ b/compiler/frontend/dune @@ -1,6 +1,8 @@ (library (name frontend) (wrapped false) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-9-40-42-70)) (libraries common ml)) diff --git a/compiler/gentype/dune b/compiler/gentype/dune index d5aa7137b20..4c6fb3af5cf 100644 --- a/compiler/gentype/dune +++ b/compiler/gentype/dune @@ -1,6 +1,8 @@ (library (name gentype) (wrapped false) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-9-40-41-42-48-70)) (libraries ml)) diff --git a/compiler/ml/dune b/compiler/ml/dune index f4dde0ef5bb..94a9c7b1039 100644 --- a/compiler/ml/dune +++ b/compiler/ml/dune @@ -1,6 +1,8 @@ (library (name ml) (wrapped false) + (instrumentation + (backend bisect_ppx)) (preprocess (action (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) diff --git a/compiler/syntax/cli/dune b/compiler/syntax/cli/dune index ba5f1ea4ce8..90903d2e792 100644 --- a/compiler/syntax/cli/dune +++ b/compiler/syntax/cli/dune @@ -9,6 +9,8 @@ (package rescript) (enabled_if (<> %{profile} browser)) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-42-40-9-48-70)) (libraries syntax)) diff --git a/compiler/syntax/src/dune b/compiler/syntax/src/dune index 7765dcbf61c..9fa08e74eb4 100644 --- a/compiler/syntax/src/dune +++ b/compiler/syntax/src/dune @@ -1,6 +1,8 @@ (library (name syntax) (wrapped false) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-42-40-9-48-70)) (libraries ml)) diff --git a/rescript.opam.template b/rescript.opam.template index e5629e01d6a..e8b2fe6abd9 100644 --- a/rescript.opam.template +++ b/rescript.opam.template @@ -8,6 +8,7 @@ depends: [ "ounit2" {with-test & = "2.2.7"} "odoc" {with-doc} "ocaml-lsp-server" {with-dev-setup & = "1.22.0"} + "bisect_ppx" {with-dev-setup & >= "2.8.0"} # Test dependencies that would be broken on Windows runners "js_of_ocaml" {os != "win32" & with-test & = "6.0.1"} diff --git a/tests/ounit_tests/dune b/tests/ounit_tests/dune index e0bcd078bcb..73bd6f0ce25 100644 --- a/tests/ounit_tests/dune +++ b/tests/ounit_tests/dune @@ -9,6 +9,8 @@ (package rescript) (enabled_if (<> %{profile} browser)) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w +a-4-9-30-40-41-42-48-70)) (libraries core ounit2 analysis)) diff --git a/tools/bin/dune b/tools/bin/dune index 674eca5b67d..f3a87dbbaf3 100644 --- a/tools/bin/dune +++ b/tools/bin/dune @@ -10,5 +10,7 @@ ; The main module that will become the binary. (name main) (libraries tools) + (instrumentation + (backend bisect_ppx)) (flags (:standard -w "+6+26+27+32+33+39"))) diff --git a/tools/src/dune b/tools/src/dune index ec90d74ab02..765cece335f 100644 --- a/tools/src/dune +++ b/tools/src/dune @@ -1,5 +1,7 @@ (library (name tools) + (instrumentation + (backend bisect_ppx)) (flags (-w "+6+26+27+32+33+39")) (libraries analysis cmarkit)) From 1efd71ea7eb26f8e9509bc7772ddeb2335b5b34f Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Thu, 7 May 2026 13:46:55 +0200 Subject: [PATCH 2/5] Move cppo preprocessing from library-wide to per-file rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four libraries that use cppo (compiler/{common,ml,core,ext}) had a library-wide `(preprocess (action (run cppo ...)))` stanza, which dune disallows in combination with `(instrumentation ...)`. That blocked adding bisect_ppx coverage on the modules where most of the type checker and error reporting lives. Of the ~250 .ml files across these four libraries, only 10 actually use cppo directives — all toggling the existing BROWSER and RELEASE flags for the JSOO playground build and stripped debug logging. The fix is mechanical: - Rename those 10 source files to `*.cppo.ml` (or `*.cppo.mli`) - Add a per-file `(rule (target X.ml) (deps X.cppo.ml) (action cppo))` for each, matching the convention compiler/ext already uses for its template files (hash_set.cppo.ml, vec.cppo.ml, etc.) - Drop the library-wide `(preprocess ...)` stanzas The same `%{env:CPPO_FLAGS=}` substitution is preserved in each rule, so the dev / release / static / browser profile behavior in `compiler/dune` is unchanged. js_reserved_map needs cppo's `-V OCAML:` flag for its `#if OCAML_VERSION >= (5, 0, 0)` directive, which is preserved on its rule specifically. Side benefit: cppo only runs on the 10 files that need it instead of every file in those libraries. --- compiler/common/dune | 3 -- compiler/core/dune | 27 +++++++++++-- ...ule_id.ml => js_name_of_module_id.cppo.ml} | 0 ...js_pass_debug.ml => js_pass_debug.cppo.ml} | 0 ...mpile_main.ml => lam_compile_main.cppo.ml} | 0 .../core/{lam_util.ml => lam_util.cppo.ml} | 0 ...bs_hash_stubs.ml => bs_hash_stubs.cppo.ml} | 0 compiler/ext/dune | 38 +++++++++++++++---- .../ext/{ext_string.ml => ext_string.cppo.ml} | 0 .../{ext_string.mli => ext_string.cppo.mli} | 0 compiler/ext/{ext_sys.ml => ext_sys.cppo.ml} | 0 ...eserved_map.ml => js_reserved_map.cppo.ml} | 0 .../ml/{cmt_format.ml => cmt_format.cppo.ml} | 0 compiler/ml/dune | 9 +++-- 14 files changed, 60 insertions(+), 17 deletions(-) rename compiler/core/{js_name_of_module_id.ml => js_name_of_module_id.cppo.ml} (100%) rename compiler/core/{js_pass_debug.ml => js_pass_debug.cppo.ml} (100%) rename compiler/core/{lam_compile_main.ml => lam_compile_main.cppo.ml} (100%) rename compiler/core/{lam_util.ml => lam_util.cppo.ml} (100%) rename compiler/ext/{bs_hash_stubs.ml => bs_hash_stubs.cppo.ml} (100%) rename compiler/ext/{ext_string.ml => ext_string.cppo.ml} (100%) rename compiler/ext/{ext_string.mli => ext_string.cppo.mli} (100%) rename compiler/ext/{ext_sys.ml => ext_sys.cppo.ml} (100%) rename compiler/ext/{js_reserved_map.ml => js_reserved_map.cppo.ml} (100%) rename compiler/ml/{cmt_format.ml => cmt_format.cppo.ml} (100%) diff --git a/compiler/common/dune b/compiler/common/dune index 1b09233e54d..247250ab20c 100644 --- a/compiler/common/dune +++ b/compiler/common/dune @@ -3,9 +3,6 @@ (wrapped false) (instrumentation (backend bisect_ppx)) - (preprocess - (action - (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) (flags (:standard -w +a-9-40-42)) (libraries syntax)) diff --git a/compiler/core/dune b/compiler/core/dune index f1fd51ab419..0e36c273a23 100644 --- a/compiler/core/dune +++ b/compiler/core/dune @@ -3,9 +3,30 @@ (wrapped false) (instrumentation (backend bisect_ppx)) - (preprocess - (action - (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) (flags (:standard -w +a-4-9-27-30-40-41-42-48-70)) (libraries depends ext flow_parser frontend gentype)) + +(rule + (target js_name_of_module_id.ml) + (deps js_name_of_module_id.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target js_pass_debug.ml) + (deps js_pass_debug.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target lam_compile_main.ml) + (deps lam_compile_main.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target lam_util.ml) + (deps lam_util.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) diff --git a/compiler/core/js_name_of_module_id.ml b/compiler/core/js_name_of_module_id.cppo.ml similarity index 100% rename from compiler/core/js_name_of_module_id.ml rename to compiler/core/js_name_of_module_id.cppo.ml diff --git a/compiler/core/js_pass_debug.ml b/compiler/core/js_pass_debug.cppo.ml similarity index 100% rename from compiler/core/js_pass_debug.ml rename to compiler/core/js_pass_debug.cppo.ml diff --git a/compiler/core/lam_compile_main.ml b/compiler/core/lam_compile_main.cppo.ml similarity index 100% rename from compiler/core/lam_compile_main.ml rename to compiler/core/lam_compile_main.cppo.ml diff --git a/compiler/core/lam_util.ml b/compiler/core/lam_util.cppo.ml similarity index 100% rename from compiler/core/lam_util.ml rename to compiler/core/lam_util.cppo.ml diff --git a/compiler/ext/bs_hash_stubs.ml b/compiler/ext/bs_hash_stubs.cppo.ml similarity index 100% rename from compiler/ext/bs_hash_stubs.ml rename to compiler/ext/bs_hash_stubs.cppo.ml diff --git a/compiler/ext/dune b/compiler/ext/dune index 868fd7dddfe..9fb08a2f048 100644 --- a/compiler/ext/dune +++ b/compiler/ext/dune @@ -3,20 +3,42 @@ (wrapped false) (instrumentation (backend bisect_ppx)) - (preprocess - (action - (run - %{bin:cppo} - -V - OCAML:%{ocaml_version} - %{env:CPPO_FLAGS=} - %{input-file}))) (flags (:standard -w +a-4-42-40-9-48-70)) (foreign_stubs (language c) (names ext_basic_hash_stubs))) +(rule + (target bs_hash_stubs.ml) + (deps bs_hash_stubs.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target js_reserved_map.ml) + (deps js_reserved_map.cppo.ml) + (action + (run %{bin:cppo} -V OCAML:%{ocaml_version} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target ext_sys.ml) + (deps ext_sys.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target ext_string.ml) + (deps ext_string.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + +(rule + (target ext_string.mli) + (deps ext_string.cppo.mli) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + (rule (targets hash_set_string.ml) (deps hash_set.cppo.ml) diff --git a/compiler/ext/ext_string.ml b/compiler/ext/ext_string.cppo.ml similarity index 100% rename from compiler/ext/ext_string.ml rename to compiler/ext/ext_string.cppo.ml diff --git a/compiler/ext/ext_string.mli b/compiler/ext/ext_string.cppo.mli similarity index 100% rename from compiler/ext/ext_string.mli rename to compiler/ext/ext_string.cppo.mli diff --git a/compiler/ext/ext_sys.ml b/compiler/ext/ext_sys.cppo.ml similarity index 100% rename from compiler/ext/ext_sys.ml rename to compiler/ext/ext_sys.cppo.ml diff --git a/compiler/ext/js_reserved_map.ml b/compiler/ext/js_reserved_map.cppo.ml similarity index 100% rename from compiler/ext/js_reserved_map.ml rename to compiler/ext/js_reserved_map.cppo.ml diff --git a/compiler/ml/cmt_format.ml b/compiler/ml/cmt_format.cppo.ml similarity index 100% rename from compiler/ml/cmt_format.ml rename to compiler/ml/cmt_format.cppo.ml diff --git a/compiler/ml/dune b/compiler/ml/dune index 94a9c7b1039..a5a53b70c3f 100644 --- a/compiler/ml/dune +++ b/compiler/ml/dune @@ -3,9 +3,12 @@ (wrapped false) (instrumentation (backend bisect_ppx)) - (preprocess - (action - (run %{bin:cppo} %{env:CPPO_FLAGS=} %{input-file}))) (flags (:standard -w +a-4-42-40-41-44-45-9-48-67-70)) (libraries ext flow_parser)) + +(rule + (target cmt_format.ml) + (deps cmt_format.cppo.ml) + (action + (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) From 0484ee16a35bbd00224cb2607d0da52738cdee9c Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Thu, 7 May 2026 13:51:04 +0200 Subject: [PATCH 3/5] Fix bisect-ppx-report flags in coverage Makefile targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flags I'd written for the report subcommands didn't match what bisect_ppx 2.8 actually accepts: - `--ignore-files ` (used for excluding generated files) does not exist. The 2.8 API takes path globs via `--expect`/ `--do-not-expect`. Dropped the regex filter for now — generated files don't add meaningful noise to the report. We can revisit excluding `compiler/ext/*_string.ml` etc. with the path-based API later if it becomes useful. - `--ignore-missing-files` is only accepted by `html` and `cobertura`, not by `summary`. Removed it from summary invocations. - `cobertura` takes the output file as a positional argument, not via `-o`. Fixed. Also pulls in the regenerated rescript.opam (`bisect_ppx` was added as a `with-dev-setup` dep in rescript.opam.template earlier; dune regenerates the .opam file on build). `make coverage-super-errors` now runs end-to-end and produces a per-file table for the type-checker / error-reporting modules. --- Makefile | 13 +++---------- rescript.opam | 1 + 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 5ccd92a6484..6834049cd2d 100644 --- a/Makefile +++ b/Makefile @@ -234,12 +234,7 @@ COVERAGE_FILES_DIR := $(COVERAGE_DIR)/files COVERAGE_HTML_DIR := $(COVERAGE_DIR)/html COVERAGE_BISECT_PREFIX := $(abspath $(COVERAGE_FILES_DIR))/bisect -# Patterns omitted from reports (frozen / vendored / cppo-generated) -COVERAGE_IGNORE := \ - --ignore-missing-files \ - --ignore-files 'compiler/ml/parsetree0\.ml' \ - --ignore-files 'compiler/ext/.*_(string|int|ident|poly|local_ident)\.ml$$' \ - --ignore-files 'compiler/ext/(hash|hash_set|map|set|vec|ordered_hash_map)\.ml$$' +COVERAGE_IGNORE := --ignore-missing-files # Re-builds the toolchain with bisect_ppx instrumentation and swaps the # instrumented binaries into BIN_DIR so any test runner that shells out to @@ -282,8 +277,7 @@ coverage-report: $(COVERAGE_IGNORE) \ -o $(COVERAGE_HTML_DIR) bisect-ppx-report summary \ - --coverage-path $(COVERAGE_FILES_DIR) \ - $(COVERAGE_IGNORE) + --coverage-path $(COVERAGE_FILES_DIR) @echo "" @echo "HTML report: $(COVERAGE_HTML_DIR)/index.html" @@ -292,7 +286,7 @@ coverage-report-cobertura: bisect-ppx-report cobertura \ --coverage-path $(COVERAGE_FILES_DIR) \ $(COVERAGE_IGNORE) \ - -o $(COVERAGE_DIR)/cobertura.xml + $(COVERAGE_DIR)/cobertura.xml .PHONY: coverage coverage: coverage-run coverage-report @@ -306,7 +300,6 @@ coverage-super-errors: coverage-run-super-errors coverage-report @echo "Per-file coverage for error-reporting modules:" @bisect-ppx-report summary --per-file \ --coverage-path $(COVERAGE_FILES_DIR) \ - $(COVERAGE_IGNORE) \ | grep -E "(error_message|typecore|typetexp|typedecl|typemod|matching|location|includemod|parmatch|ast_untagged_variants)" \ || true diff --git a/rescript.opam b/rescript.opam index 40a9251350f..c19070af555 100644 --- a/rescript.opam +++ b/rescript.opam @@ -30,6 +30,7 @@ depends: [ "ounit2" {with-test & = "2.2.7"} "odoc" {with-doc} "ocaml-lsp-server" {with-dev-setup & = "1.22.0"} + "bisect_ppx" {with-dev-setup & >= "2.8.0"} # Test dependencies that would be broken on Windows runners "js_of_ocaml" {os != "win32" & with-test & = "6.0.1"} From e49289559bf96e27faf8c5e23fa5a241f67e6f12 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Thu, 7 May 2026 14:07:16 +0200 Subject: [PATCH 4/5] Update .ocamlformat-ignore for renamed cppo files ocamlformat was failing on the renamed .cppo.ml/.mli files because the ignore list still referenced their old paths. Replaced the explicit file list with `**/*.cppo.ml` and `**/*.cppo.mli` globs, which covers the renamed files plus the existing `compiler/ext/*.cppo.ml` templates that were already listed individually. Future cppo files added under the new convention are covered automatically. Also includes the auto-promoted dune format applied to the js_reserved_map rule in compiler/ext/dune (`dune build @fmt` reformatted the multi-arg cppo invocation onto one arg per line). --- .ocamlformat-ignore | 18 ++---------------- compiler/ext/dune | 9 ++++++++- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/.ocamlformat-ignore b/.ocamlformat-ignore index d228f90a37a..f58b14a5225 100644 --- a/.ocamlformat-ignore +++ b/.ocamlformat-ignore @@ -1,18 +1,4 @@ compiler/js_parser/** -compiler/ml/cmt_format.ml -compiler/core/js_name_of_module_id.ml -compiler/core/js_pass_debug.ml -compiler/core/lam_util.ml -compiler/core/lam_compile_main.ml -compiler/ext/bs_hash_stubs.ml -compiler/ext/js_reserved_map.ml -compiler/ext/ext_string.ml -compiler/ext/ext_string.mli -compiler/ext/ext_sys.ml -compiler/ext/hash.cppo.ml -compiler/ext/hash_set.cppo.ml -compiler/ext/map.cppo.ml -compiler/ext/ordered_hash_map.cppo.ml -compiler/ext/set.cppo.ml -compiler/ext/vec.cppo.ml +**/*.cppo.ml +**/*.cppo.mli compiler/syntax/compiler-libs-406/* diff --git a/compiler/ext/dune b/compiler/ext/dune index 9fb08a2f048..c6a8a9dfeb9 100644 --- a/compiler/ext/dune +++ b/compiler/ext/dune @@ -19,7 +19,14 @@ (target js_reserved_map.ml) (deps js_reserved_map.cppo.ml) (action - (run %{bin:cppo} -V OCAML:%{ocaml_version} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) + (run + %{bin:cppo} + -V + OCAML:%{ocaml_version} + %{env:CPPO_FLAGS=} + %{deps} + -o + %{target}))) (rule (target ext_sys.ml) From a36eab0b2a13c1ada70d3d275e7beea1f97289f1 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Thu, 7 May 2026 14:29:37 +0200 Subject: [PATCH 5/5] Simplify coverage Makefile to one user-facing target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trim the coverage surface to what's actually needed for local inspection + agent queries: - Remove `coverage-report-cobertura` — Cobertura XML is only useful for CI dashboards (Codecov/Coveralls upload). Not wiring CI for now. - Remove `coverage-super-errors` and `coverage-run-super-errors` — super_errors fixtures are already executed under `make coverage` via scripts/test.js -all (which iterates tests/build_tests/*). Querying for super_errors-relevant modules is easy with jq on coverage.json or a grep on `summary --per-file`. - Add `coveralls` JSON output to `coverage-report`. Goes to _coverage/coverage.json with the structure documented in the Makefile header so an agent / jq one-liner can consume it directly. - Inline the COVERAGE_IGNORE variable since `--ignore-missing-files` is the only flag now. User-facing surface: `make coverage`, `make clean-coverage`, plus internal phases (coverage-build, coverage-prepare, coverage-lib, coverage-run, coverage-report) for re-running individual stages. --- Makefile | 52 +++++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 6834049cd2d..7239353e8fb 100644 --- a/Makefile +++ b/Makefile @@ -225,20 +225,29 @@ checkformat: | $(YARN_INSTALL_STAMP) # opam install . --deps-only --with-dev-setup # # Quick start: -# make coverage-super-errors # focused report for super-errors fixtures -# make coverage # full report across all test suites -# make clean-coverage # remove all coverage artifacts +# make coverage # run full test suite, generate report +# make clean-coverage # remove coverage artifacts +# +# Outputs (under _coverage/): +# html/index.html — human-browsable line-level report +# coverage.json — Coveralls-format JSON, queryable with jq: +# { source_files: [{ name, coverage: [null|N, ...] }] } +# null = not instrumented, 0 = uncovered, N > 0 = hit count +# e.g. uncovered line numbers in one file: +# jq -r --arg f compiler/ml/typecore.ml \ +# '.source_files[] | select(.name==$f) | .coverage +# | to_entries[] | select(.value==0) | (.key+1)' \ +# _coverage/coverage.json COVERAGE_DIR := _coverage COVERAGE_FILES_DIR := $(COVERAGE_DIR)/files COVERAGE_HTML_DIR := $(COVERAGE_DIR)/html +COVERAGE_JSON := $(COVERAGE_DIR)/coverage.json COVERAGE_BISECT_PREFIX := $(abspath $(COVERAGE_FILES_DIR))/bisect -COVERAGE_IGNORE := --ignore-missing-files - # Re-builds the toolchain with bisect_ppx instrumentation and swaps the # instrumented binaries into BIN_DIR so any test runner that shells out to -# `bsc` produces .coverage files. Skips `strip` to keep debug info intact. +# `bsc` produces .coverage files. .PHONY: coverage-build coverage-build: | $(YARN_INSTALL_STAMP) dune build --instrument-with bisect_ppx @@ -265,44 +274,25 @@ coverage-run: coverage-lib BISECT_FILE=$(COVERAGE_BISECT_PREFIX) BISECT_SILENT=YES \ node scripts/test.js -all -.PHONY: coverage-run-super-errors -coverage-run-super-errors: coverage-prepare - BISECT_FILE=$(COVERAGE_BISECT_PREFIX) BISECT_SILENT=YES \ - node tests/build_tests/super_errors/input.js - .PHONY: coverage-report coverage-report: bisect-ppx-report html \ --coverage-path $(COVERAGE_FILES_DIR) \ - $(COVERAGE_IGNORE) \ + --ignore-missing-files \ -o $(COVERAGE_HTML_DIR) + bisect-ppx-report coveralls \ + --coverage-path $(COVERAGE_FILES_DIR) \ + --ignore-missing-files \ + $(COVERAGE_JSON) bisect-ppx-report summary \ --coverage-path $(COVERAGE_FILES_DIR) @echo "" @echo "HTML report: $(COVERAGE_HTML_DIR)/index.html" - -.PHONY: coverage-report-cobertura -coverage-report-cobertura: - bisect-ppx-report cobertura \ - --coverage-path $(COVERAGE_FILES_DIR) \ - $(COVERAGE_IGNORE) \ - $(COVERAGE_DIR)/cobertura.xml + @echo "JSON data: $(COVERAGE_JSON)" .PHONY: coverage coverage: coverage-run coverage-report -# Focused report for the super-errors fixtures. Prints per-file coverage for -# the type-checker / error-reporting modules so it's easy to spot which error -# paths are unexercised. -.PHONY: coverage-super-errors -coverage-super-errors: coverage-run-super-errors coverage-report - @echo "" - @echo "Per-file coverage for error-reporting modules:" - @bisect-ppx-report summary --per-file \ - --coverage-path $(COVERAGE_FILES_DIR) \ - | grep -E "(error_message|typecore|typetexp|typedecl|typemod|matching|location|includemod|parmatch|ast_untagged_variants)" \ - || true - .PHONY: clean-coverage clean-coverage: rm -rf $(COVERAGE_DIR)