From b9bcc5d06e093b1b0148ec935f5e84f058f109af Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:30:16 +0000 Subject: [PATCH 1/6] tool: add C coverage harness Make coverage collection reproducible without changing the vendored suite or implementation. --- .gitignore | 4 + scripts/run-coverage-c.sh | 276 ++++++++++++++++++++++++++++++++++++++ tools/coverage_driver.c | 73 ++++++++++ 3 files changed, 353 insertions(+) create mode 100644 scripts/run-coverage-c.sh create mode 100644 tools/coverage_driver.c diff --git a/.gitignore b/.gitignore index 21147d9..a7def7b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,12 @@ repl *.gcno *.gcov coverage/ +.coverage-c/ *.info htmlcov/ +docs/evidence/coverage-c-html/ +!docs/evidence/coverage-c-upstream.info +!docs/evidence/coverage-c-expanded.info # Fuzz / bench runtime output (seed corpus is tracked) fuzz/logs/ diff --git a/scripts/run-coverage-c.sh b/scripts/run-coverage-c.sh new file mode 100644 index 0000000..5eb8a07 --- /dev/null +++ b/scripts/run-coverage-c.sh @@ -0,0 +1,276 @@ +#!/usr/bin/env bash +# Measure coverage of the vendored C original without modifying its sources. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +EVIDENCE="$ROOT/docs/evidence" +WORK="$ROOT/.coverage-c" +HTML="$EVIDENCE/coverage-c-html" +UPSTREAM_INFO="$EVIDENCE/coverage-c-upstream.info" +EXPANDED_INFO="$EVIDENCE/coverage-c-expanded.info" +FRAGMENT="$EVIDENCE/fragments/coverage-c.json" +LOG="$EVIDENCE/coverage-c.log" +SOURCE="$ROOT/third_party/tinyexpr-c/tinyexpr.c" +INCLUDE="$ROOT/third_party/tinyexpr-c" +SMOKE="$ROOT/tests/original/smoke.c" +DRIVER="$ROOT/tools/coverage_driver.c" + +mkdir -p "$EVIDENCE/fragments" "$WORK" "$HTML" +exec > >(tee "$LOG") 2>&1 + +printf -v COMMAND_LINE '%q ' "$0" "$@" +COMMAND_LINE="${COMMAND_LINE% }" +STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +GIT_SHA="$(git -C "$ROOT" rev-parse HEAD)" +HOST="$(uname -a)" +GCC_VERSION="$(gcc --version)" +CFLAGS=(--coverage -fprofile-arcs -ftest-coverage -O0 -I"$INCLUDE") + +LCOV="" +GENHTML="" +setup_lcov() { + if command -v lcov >/dev/null 2>&1 && command -v genhtml >/dev/null 2>&1; then + LCOV="$(command -v lcov)" + GENHTML="$(command -v genhtml)" + return 0 + fi + + local prefix="$HOME/.local/lcov" + local local_lcov="$prefix/usr/bin/lcov" + local local_genhtml="$prefix/usr/bin/genhtml" + if [[ -x "$local_lcov" && -x "$local_genhtml" ]]; then + LCOV="$local_lcov" + GENHTML="$local_genhtml" + return 0 + fi + + echo "lcov/genhtml not found; extracting the Ubuntu lcov package to $prefix" + local download_dir + download_dir="$(mktemp -d)" + if ( + cd "$download_dir" + apt-get download lcov + shopt -s nullglob + local packages=(lcov_*.deb) + [[ ${#packages[@]} -eq 1 ]] + mkdir -p "$prefix" + dpkg-deb -x "${packages[0]}" "$prefix" + ); then + rm -rf "$download_dir" + if [[ -x "$local_lcov" && -x "$local_genhtml" ]]; then + LCOV="$local_lcov" + GENHTML="$local_genhtml" + return 0 + fi + else + rm -rf "$download_dir" + fi + + echo "WARNING: lcov installation failed; falling back to gcov text reports." + return 1 +} + +HAVE_LCOV=0 +if setup_lcov; then + HAVE_LCOV=1 +fi + +LCOV_VERSION="unavailable" +if [[ "$HAVE_LCOV" -eq 1 ]]; then + LCOV_VERSION="$("$LCOV" --version 2>&1)" +fi + +build_mode() { + local mode="$1" + local directory="$WORK/$mode" + rm -rf "$directory" + mkdir -p "$directory" + + gcc "${CFLAGS[@]}" -c "$SOURCE" -o "$directory/tinyexpr.o" + if [[ "$mode" == "upstream" ]]; then + gcc "${CFLAGS[@]}" -c "$SMOKE" -o "$directory/smoke.o" + gcc --coverage -O0 "$directory/tinyexpr.o" "$directory/smoke.o" -lm -o "$directory/smoke" + else + gcc "${CFLAGS[@]}" -c "$DRIVER" -o "$directory/coverage_driver.o" + gcc --coverage -O0 "$directory/tinyexpr.o" "$directory/coverage_driver.o" -lm -o "$directory/coverage_driver" + fi +} + +capture_coverage() { + local mode="$1" + local info="$2" + local title="$3" + local directory="$WORK/$mode" + + if [[ "$HAVE_LCOV" -eq 1 ]]; then + local raw="$directory/all.info" + "$LCOV" --rc branch_coverage=1 --capture \ + --directory "$directory" \ + --base-directory "$ROOT" \ + --output-file "$raw" + "$LCOV" --rc branch_coverage=1 --extract "$raw" "$SOURCE" --output-file "$info" + "$LCOV" --rc branch_coverage=1 --summary "$info" + rm -rf "$HTML/$mode" + "$GENHTML" --rc branch_coverage=1 "$info" \ + --output-directory "$HTML/$mode" \ + --prefix "$ROOT" \ + --title "$title" + else + ( + cd "$directory" + gcov -b -c -o . "$SOURCE" + ) | tee "$EVIDENCE/coverage-c-$mode.gcov.txt" + fi +} + +make_expanded_corpus() { + local corpus="$1" + python3 - "$ROOT" "$corpus" <<'PY' +import json +import re +import sys +from pathlib import Path + +root = Path(sys.argv[1]) +output = Path(sys.argv[2]) +expressions = [] + +for fixture in sorted((root / "tests/port/fixtures").glob("*.jsonl")): + for raw in fixture.read_text(encoding="utf-8").splitlines(): + if not raw: + continue + expression = json.loads(raw).get("expression") + if expression is not None: + expressions.append(expression) + +for seed in sorted((root / "fuzz/corpus-seed").iterdir()): + if seed.is_file(): + expressions.extend(seed.read_text(encoding="utf-8").splitlines()) + +# Smoke's string literals include every fixed expression from its test tables. +# Identifier strings used for test lookup tables are retained as error inputs. +smoke = (root / "tests/original/smoke.c").read_text(encoding="utf-8") +for literal in re.findall(r'"((?:\\.|[^"\\])*)"', smoke): + expressions.append(bytes(literal, "utf-8").decode("unicode_escape")) + +with output.open("w", encoding="utf-8", newline="\n") as stream: + for expression in expressions: + # The driver is line-oriented; preserve parser coverage for embedded + # whitespace without allowing one expression to create extra records. + stream.write(expression.replace("\r", " ").replace("\n", " ") + "\n") + +print(len(expressions)) +PY +} + +echo "=== C coverage metadata ===" +echo "started_at_utc=$STARTED_AT" +echo "git_sha=$GIT_SHA" +echo "host=$HOST" +echo "gcc_version=$GCC_VERSION" +echo "lcov_version=$LCOV_VERSION" +echo "command_line=$COMMAND_LINE" +echo "cflags=${CFLAGS[*]}" + +echo "=== upstream-suite-only baseline ===" +build_mode upstream +"$WORK/upstream/smoke" +capture_coverage upstream "$UPSTREAM_INFO" "tinyexpr C upstream-suite-only coverage" + +echo "=== expanded corpus ===" +build_mode expanded +CORPUS="$WORK/expanded-input.txt" +CORPUS_COUNT="$(make_expanded_corpus "$CORPUS")" +echo "expanded_corpus_expressions=$CORPUS_COUNT" +"$WORK/expanded/coverage_driver" --file "$CORPUS" +capture_coverage expanded "$EXPANDED_INFO" "tinyexpr C expanded-corpus coverage" + +COMPLETED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +if [[ "$HAVE_LCOV" -eq 1 ]]; then + python3 - "$UPSTREAM_INFO" "$EXPANDED_INFO" "$FRAGMENT" "$ROOT" "$STARTED_AT" "$COMPLETED_AT" "$GIT_SHA" "$HOST" "$GCC_VERSION" "$LCOV_VERSION" "$COMMAND_LINE" <<'PY' +import json +import sys +from pathlib import Path + +upstream_info, expanded_info, fragment, root, started, completed, sha, host, gcc, lcov, command = sys.argv[1:] + +def read_counts(info_path): + counts = { + "lines": {"hit": 0, "found": 0}, + "functions": {"hit": 0, "found": 0}, + "branches": {"hit": 0, "found": 0}, + } + for raw in Path(info_path).read_text(encoding="utf-8").splitlines(): + if raw.startswith("DA:"): + _, payload = raw.split(":", 1) + _, hits, *_ = payload.split(",") + counts["lines"]["found"] += 1 + counts["lines"]["hit"] += int(hits) > 0 + elif raw.startswith("FNDA:"): + _, payload = raw.split(":", 1) + hits, _ = payload.split(",", 1) + counts["functions"]["found"] += 1 + counts["functions"]["hit"] += int(hits) > 0 + elif raw.startswith("BRDA:"): + _, payload = raw.split(":", 1) + _, _, _, hits = payload.split(",", 3) + counts["branches"]["found"] += 1 + counts["branches"]["hit"] += hits != "-" and int(hits) > 0 + for metric in counts.values(): + metric["percent"] = round(100 * metric["hit"] / metric["found"], 2) if metric["found"] else 100.0 + return counts + +result = { + "schema_version": 1, + "subject": "third_party/tinyexpr-c/tinyexpr.c", + "coverage_tool": "lcov", + "command_line": command, + "git_sha": sha, + "host": host, + "compiler": {"gcc": gcc, "lcov": lcov}, + "started_at_utc": started, + "completed_at_utc": completed, + "modes": { + "upstream_suite_only": { + "info": "docs/evidence/coverage-c-upstream.info", + "html": "docs/evidence/coverage-c-html/upstream/", + "counts": read_counts(upstream_info), + }, + "expanded_corpus": { + "info": "docs/evidence/coverage-c-expanded.info", + "html": "docs/evidence/coverage-c-html/expanded/", + "counts": read_counts(expanded_info), + }, + }, +} +Path(fragment).write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") +PY +else + python3 - "$FRAGMENT" "$STARTED_AT" "$COMPLETED_AT" "$GIT_SHA" "$HOST" "$GCC_VERSION" "$COMMAND_LINE" <<'PY' +import json +import sys +from pathlib import Path + +fragment, started, completed, sha, host, gcc, command = sys.argv[1:] +result = { + "schema_version": 1, + "subject": "third_party/tinyexpr-c/tinyexpr.c", + "coverage_tool": "gcov_fallback", + "status": "lcov_install_failed", + "command_line": command, + "git_sha": sha, + "host": host, + "compiler": {"gcc": gcc}, + "started_at_utc": started, + "completed_at_utc": completed, + "reports": [ + "docs/evidence/coverage-c-upstream.gcov.txt", + "docs/evidence/coverage-c-expanded.gcov.txt", + ], +} +Path(fragment).write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") +PY +fi + +echo "completed_at_utc=$COMPLETED_AT" +echo "coverage evidence written under docs/evidence/" diff --git a/tools/coverage_driver.c b/tools/coverage_driver.c new file mode 100644 index 0000000..04e525a --- /dev/null +++ b/tools/coverage_driver.c @@ -0,0 +1,73 @@ +/* Coverage harness for tinyexpr-c: execute both public parse entry points. */ +#define _POSIX_C_SOURCE 200809L + +#include "tinyexpr.h" + +#include +#include +#include + +static int run_lines(FILE *input, const char *source_name) { + char *line = NULL; + size_t capacity = 0; + unsigned long long total = 0; + unsigned long long interp_ok = 0; + unsigned long long compile_ok = 0; + + while (getline(&line, &capacity, input) != -1) { + size_t length = strlen(line); + while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) { + line[--length] = '\0'; + } + + int interp_error = 0; + volatile double interp_value = te_interp(line, &interp_error); + (void)interp_value; + if (interp_error == 0) ++interp_ok; + + int compile_error = 0; + te_expr *expression = te_compile(line, NULL, 0, &compile_error); + if (expression != NULL) { + volatile double compiled_value = te_eval(expression); + (void)compiled_value; + ++compile_ok; + te_free(expression); + } + + ++total; + } + + free(line); + if (ferror(input)) { + fprintf(stderr, "coverage driver: failed reading %s\n", source_name); + return 1; + } + + fprintf( + stderr, + "coverage driver: source=%s total=%llu interp_ok=%llu compile_ok=%llu\n", + source_name, + total, + interp_ok, + compile_ok + ); + return 0; +} + +int main(int argc, char **argv) { + if (argc == 1) return run_lines(stdin, "stdin"); + + if (argc == 3 && (strcmp(argv[1], "--file") == 0 || strcmp(argv[1], "-f") == 0)) { + FILE *input = fopen(argv[2], "r"); + if (input == NULL) { + perror(argv[2]); + return 1; + } + const int status = run_lines(input, argv[2]); + fclose(input); + return status; + } + + fprintf(stderr, "usage: %s [--file CORPUS]\n", argv[0]); + return 2; +} From 33837ec06aecb68f23190594923c3fb52400fb37 Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:34:13 +0000 Subject: [PATCH 2/6] tool: resolve local lcov dependencies Extract the transitive Perl runtime dependencies when lcov is installed without sudo. --- scripts/run-coverage-c.sh | 41 +++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/scripts/run-coverage-c.sh b/scripts/run-coverage-c.sh index 5eb8a07..3db092d 100644 --- a/scripts/run-coverage-c.sh +++ b/scripts/run-coverage-c.sh @@ -28,6 +28,16 @@ CFLAGS=(--coverage -fprofile-arcs -ftest-coverage -O0 -I"$INCLUDE") LCOV="" GENHTML="" +LOCAL_LCOV_PREFIX="$HOME/.local/lcov" +use_local_lcov() { + LCOV="$LOCAL_LCOV_PREFIX/usr/bin/lcov" + GENHTML="$LOCAL_LCOV_PREFIX/usr/bin/genhtml" + local perl_vendor_arch + perl_vendor_arch="$(perl -MConfig -e 'print $Config{vendorarch}')" + export PERL5LIB="$LOCAL_LCOV_PREFIX/usr/lib/lcov:$LOCAL_LCOV_PREFIX/usr/share/perl5:$LOCAL_LCOV_PREFIX$perl_vendor_arch${PERL5LIB:+:$PERL5LIB}" + "$LCOV" --version >/dev/null 2>&1 && "$GENHTML" --version >/dev/null 2>&1 +} + setup_lcov() { if command -v lcov >/dev/null 2>&1 && command -v genhtml >/dev/null 2>&1; then LCOV="$(command -v lcov)" @@ -35,32 +45,39 @@ setup_lcov() { return 0 fi - local prefix="$HOME/.local/lcov" + local prefix="$LOCAL_LCOV_PREFIX" local local_lcov="$prefix/usr/bin/lcov" local local_genhtml="$prefix/usr/bin/genhtml" if [[ -x "$local_lcov" && -x "$local_genhtml" ]]; then - LCOV="$local_lcov" - GENHTML="$local_genhtml" - return 0 + if use_local_lcov; then + return 0 + fi fi - echo "lcov/genhtml not found; extracting the Ubuntu lcov package to $prefix" + echo "lcov/genhtml not found; extracting lcov and its Perl dependencies to $prefix" local download_dir download_dir="$(mktemp -d)" if ( cd "$download_dir" - apt-get download lcov + apt-cache depends --recurse --no-recommends lcov | + awk '/^[[:space:]]*[|]?(PreDepends|Depends): / { print $2 }' | + awk '!/^ packages.txt + mapfile -t packages < packages.txt + apt-get download lcov "${packages[@]}" shopt -s nullglob - local packages=(lcov_*.deb) - [[ ${#packages[@]} -eq 1 ]] + packages=(*.deb) + [[ ${#packages[@]} -gt 0 ]] mkdir -p "$prefix" - dpkg-deb -x "${packages[0]}" "$prefix" + for package in "${packages[@]}"; do + dpkg-deb -x "$package" "$prefix" + done ); then rm -rf "$download_dir" if [[ -x "$local_lcov" && -x "$local_genhtml" ]]; then - LCOV="$local_lcov" - GENHTML="$local_genhtml" - return 0 + if use_local_lcov; then + return 0 + fi fi else rm -rf "$download_dir" From 4bd80fd433010b6bf5b8a6eace5b822bbb8f9eb3 Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:42:59 +0000 Subject: [PATCH 3/6] cover: record original C coverage baseline Capture separate upstream-suite and expanded-corpus lcov measurements with reproducible evidence. --- docs/evidence/coverage-c-expanded.info | 674 ++++++++++++++++++++++++ docs/evidence/coverage-c-upstream.info | 674 ++++++++++++++++++++++++ docs/evidence/coverage-c.log | 178 +++++++ docs/evidence/coverage.md | 53 ++ docs/evidence/fragments/coverage-c.json | 58 ++ scripts/run-coverage-c.sh | 126 ++++- tools/coverage_driver.c | 112 +++- 7 files changed, 1854 insertions(+), 21 deletions(-) create mode 100644 docs/evidence/coverage-c-expanded.info create mode 100644 docs/evidence/coverage-c-upstream.info create mode 100644 docs/evidence/coverage-c.log create mode 100644 docs/evidence/coverage.md create mode 100644 docs/evidence/fragments/coverage-c.json diff --git a/docs/evidence/coverage-c-expanded.info b/docs/evidence/coverage-c-expanded.info new file mode 100644 index 0000000..d627b28 --- /dev/null +++ b/docs/evidence/coverage-c-expanded.info @@ -0,0 +1,674 @@ +TN: +SF:/mnt/d/hackathons/portmortem/tinyexpr.zig/third_party/tinyexpr-c/tinyexpr.c +FN:104,115,te_free_parameters +FN:118,122,te_free +FN:125,125,pi +FN:126,126,e +FN:127,140,fac +FN:141,154,ncr +FN:155,155,npr +FN:195,214,find_builtin +FN:216,227,find_lookup +FN:231,231,add +FN:232,232,sub +FN:233,233,mul +FN:234,234,divide +FN:235,235,negate +FN:236,236,comma +FN:239,303,next_token +FN:310,419,base +FN:422,445,power +FN:503,522,factor +FN:527,546,term +FN:549,568,expr +FN:571,589,list +FN:596,634,te_eval +FN:639,662,optimize +FN:665,690,te_compile +FN:693,704,te_interp +FN:706,729,pn +FN:732,734,te_print +FN:87,101,new_expr +FNDA:27612,te_free_parameters +FNDA:22053,te_free +FNDA:49,pi +FNDA:44,e +FNDA:582,fac +FNDA:146,ncr +FNDA:64,npr +FNDA:5673,find_builtin +FNDA:9522,find_lookup +FNDA:706,add +FNDA:448,sub +FNDA:558,mul +FNDA:475,divide +FNDA:285,negate +FNDA:146,comma +FNDA:44669,next_token +FNDA:23441,base +FNDA:23441,power +FNDA:20079,factor +FNDA:18516,term +FNDA:17183,expr +FNDA:13290,list +FNDA:24650,te_eval +FNDA:18835,optimize +FNDA:7783,te_compile +FNDA:2800,te_interp +FNDA:73,pn +FNDA:18,te_print +FNDA:21982,new_expr +FNF:29 +FNH:29 +BRDA:88,0,0,8726 +BRDA:88,0,1,13256 +BRDA:92,0,0,0 +BRDA:92,0,1,21982 +BRDA:95,0,0,8601 +BRDA:95,0,1,13381 +BRDA:95,0,2,4048 +BRDA:95,0,3,4553 +BRDA:105,0,0,0 +BRDA:105,0,1,27612 +BRDA:106,0,0,4 +BRDA:106,0,1,3 +BRDA:106,0,2,3 +BRDA:106,0,3,3 +BRDA:106,0,4,7 +BRDA:106,0,5,5595 +BRDA:106,0,6,2986 +BRDA:106,0,7,19011 +BRDA:119,0,0,71 +BRDA:119,0,1,21982 +BRDA:128,0,0,8 +BRDA:128,0,1,574 +BRDA:130,0,0,1 +BRDA:130,0,1,573 +BRDA:134,0,0,2008 +BRDA:134,0,1,561 +BRDA:135,0,0,12 +BRDA:135,0,1,1996 +BRDA:142,0,0,138 +BRDA:142,0,1,8 +BRDA:142,0,2,130 +BRDA:142,0,3,8 +BRDA:142,0,4,8 +BRDA:142,0,5,122 +BRDA:143,0,0,121 +BRDA:143,0,1,1 +BRDA:143,0,2,0 +BRDA:143,0,3,121 +BRDA:146,0,0,26 +BRDA:146,0,1,95 +BRDA:147,0,0,428 +BRDA:147,0,1,105 +BRDA:148,0,0,16 +BRDA:148,0,1,412 +BRDA:200,0,0,25812 +BRDA:200,0,1,1016 +BRDA:203,0,0,5821 +BRDA:203,0,1,19991 +BRDA:204,0,0,4657 +BRDA:204,0,1,21155 +BRDA:206,0,0,14137 +BRDA:206,0,1,7018 +BRDA:219,0,0,3462 +BRDA:219,0,1,6060 +BRDA:221,0,0,10096 +BRDA:221,0,1,2211 +BRDA:222,0,0,3849 +BRDA:222,0,1,6247 +BRDA:222,0,2,3849 +BRDA:222,0,3,0 +BRDA:244,0,0,6241 +BRDA:244,0,1,39341 +BRDA:250,0,0,18116 +BRDA:250,0,1,21225 +BRDA:250,0,2,10293 +BRDA:250,0,3,7823 +BRDA:250,0,4,67 +BRDA:250,0,5,31451 +BRDA:255,0,0,9522 +BRDA:255,0,1,21929 +BRDA:258,0,0,21470 +BRDA:258,0,1,10947 +BRDA:258,0,2,1366 +BRDA:258,0,3,9581 +BRDA:258,0,4,59 +BRDA:258,0,5,9522 +BRDA:261,0,0,5673 +BRDA:261,0,1,3849 +BRDA:263,0,0,1016 +BRDA:263,0,1,8506 +BRDA:266,0,0,3794 +BRDA:266,0,1,21 +BRDA:266,0,2,4691 +BRDA:266,0,3,0 +BRDA:287,0,0,2240 +BRDA:287,0,1,855 +BRDA:287,0,2,710 +BRDA:287,0,3,519 +BRDA:287,0,4,722 +BRDA:287,0,5,462 +BRDA:287,0,6,7435 +BRDA:287,0,7,5973 +BRDA:287,0,8,2019 +BRDA:287,0,9,913 +BRDA:287,0,10,81 +BRDA:302,0,0,913 +BRDA:302,0,1,38428 +BRDA:315,0,0,7870 +BRDA:315,0,1,3794 +BRDA:315,0,2,125 +BRDA:315,0,3,2673 +BRDA:315,0,4,1880 +BRDA:315,0,5,5507 +BRDA:315,0,6,1592 +BRDA:318,0,0,0 +BRDA:318,0,1,7870 +BRDA:326,0,0,0 +BRDA:326,0,1,3794 +BRDA:335,0,0,0 +BRDA:335,0,1,125 +BRDA:338,0,0,5 +BRDA:338,0,1,120 +BRDA:340,0,0,7 +BRDA:340,0,1,118 +BRDA:342,0,0,2 +BRDA:342,0,1,5 +BRDA:353,0,0,0 +BRDA:353,0,1,2673 +BRDA:356,0,0,9 +BRDA:356,0,1,2664 +BRDA:359,0,0,0 +BRDA:359,0,1,2673 +BRDA:366,0,0,1880 +BRDA:366,0,1,0 +BRDA:369,0,0,0 +BRDA:369,0,1,1880 +BRDA:372,0,0,7 +BRDA:372,0,1,1873 +BRDA:375,0,0,1 +BRDA:375,0,1,1879 +BRDA:379,0,0,3743 +BRDA:379,0,1,3 +BRDA:382,0,0,0 +BRDA:382,0,1,3743 +BRDA:384,0,0,1876 +BRDA:384,0,1,1867 +BRDA:388,0,0,1812 +BRDA:388,0,1,67 +BRDA:388,0,2,8 +BRDA:388,0,3,1804 +BRDA:400,0,0,0 +BRDA:400,0,1,5507 +BRDA:402,0,0,1526 +BRDA:402,0,1,3981 +BRDA:411,0,0,0 +BRDA:411,0,1,1592 +BRDA:425,0,0,1923 +BRDA:425,0,1,23280 +BRDA:425,0,2,1369 +BRDA:425,0,3,554 +BRDA:425,0,4,393 +BRDA:425,0,5,161 +BRDA:426,0,0,393 +BRDA:426,0,1,1369 +BRDA:432,0,0,23128 +BRDA:432,0,1,313 +BRDA:436,0,0,0 +BRDA:436,0,1,313 +BRDA:439,0,0,0 +BRDA:439,0,1,313 +BRDA:506,0,0,0 +BRDA:506,0,1,20079 +BRDA:508,0,0,3585 +BRDA:508,0,1,17183 +BRDA:508,0,2,689 +BRDA:508,0,3,2896 +BRDA:512,0,0,0 +BRDA:512,0,1,689 +BRDA:516,0,0,0 +BRDA:516,0,1,689 +BRDA:530,0,0,0 +BRDA:530,0,1,18516 +BRDA:532,0,0,2896 +BRDA:532,0,1,17183 +BRDA:532,0,2,626 +BRDA:532,0,3,2270 +BRDA:532,0,4,497 +BRDA:532,0,5,1773 +BRDA:532,0,6,440 +BRDA:532,0,7,1333 +BRDA:536,0,0,0 +BRDA:536,0,1,1563 +BRDA:540,0,0,0 +BRDA:540,0,1,1563 +BRDA:552,0,0,0 +BRDA:552,0,1,17183 +BRDA:554,0,0,1333 +BRDA:554,0,1,17183 +BRDA:554,0,2,871 +BRDA:554,0,3,462 +BRDA:554,0,4,462 +BRDA:554,0,5,0 +BRDA:558,0,0,0 +BRDA:558,0,1,1333 +BRDA:562,0,0,0 +BRDA:562,0,1,1333 +BRDA:574,0,0,0 +BRDA:574,0,1,13290 +BRDA:576,0,0,150 +BRDA:576,0,1,13290 +BRDA:579,0,0,0 +BRDA:579,0,1,150 +BRDA:583,0,0,0 +BRDA:583,0,1,150 +BRDA:597,0,0,0 +BRDA:597,0,1,24650 +BRDA:599,0,0,12866 +BRDA:599,0,1,3830 +BRDA:599,0,2,7932 +BRDA:599,0,3,22 +BRDA:599,0,4,0 +BRDA:605,0,0,7932 +BRDA:605,0,1,0 +BRDA:605,0,2,106 +BRDA:605,0,3,2622 +BRDA:605,0,4,5193 +BRDA:605,0,5,3 +BRDA:605,0,6,2 +BRDA:605,0,7,2 +BRDA:605,0,8,2 +BRDA:605,0,9,2 +BRDA:605,0,10,0 +BRDA:619,0,0,22 +BRDA:619,0,1,0 +BRDA:619,0,2,4 +BRDA:619,0,3,10 +BRDA:619,0,4,3 +BRDA:619,0,5,1 +BRDA:619,0,6,1 +BRDA:619,0,7,1 +BRDA:619,0,8,1 +BRDA:619,0,9,1 +BRDA:619,0,10,0 +BRDA:641,0,0,7134 +BRDA:641,0,1,11701 +BRDA:642,0,0,3790 +BRDA:642,0,1,7911 +BRDA:645,0,0,7866 +BRDA:645,0,1,45 +BRDA:646,0,0,7866 +BRDA:646,0,1,0 +BRDA:649,0,0,12936 +BRDA:649,0,1,7866 +BRDA:651,0,0,3815 +BRDA:651,0,1,9121 +BRDA:655,0,0,5630 +BRDA:655,0,1,2236 +BRDA:673,0,0,0 +BRDA:673,0,1,7783 +BRDA:674,0,0,- +BRDA:674,0,1,- +BRDA:678,0,0,1884 +BRDA:678,0,1,5899 +BRDA:680,0,0,1870 +BRDA:680,0,1,14 +BRDA:682,0,0,10 +BRDA:682,0,1,1860 +BRDA:687,0,0,5871 +BRDA:687,0,1,28 +BRDA:697,0,0,1856 +BRDA:697,0,1,944 +BRDA:710,0,0,56 +BRDA:710,0,1,2 +BRDA:710,0,2,15 +BRDA:710,0,3,0 +BRDA:718,0,0,15 +BRDA:718,0,1,0 +BRDA:720,0,0,55 +BRDA:720,0,1,15 +BRDA:724,0,0,55 +BRDA:724,0,1,15 +BRF:271 +BRH:229 +DA:87,21982 +DA:88,21982 +DA:89,21982 +DA:90,21982 +DA:91,21982 +DA:92,21982 +DA:94,21982 +DA:95,21982 +DA:96,4048 +DA:98,21982 +DA:99,21982 +DA:100,21982 +DA:104,27612 +DA:105,27612 +DA:106,27612 +DA:107,4 +DA:108,7 +DA:109,10 +DA:110,13 +DA:111,20 +DA:112,5615 +DA:113,8601 +DA:118,22053 +DA:119,22053 +DA:120,21982 +DA:121,21982 +DA:125,49 +DA:126,44 +DA:127,582 +DA:128,582 +DA:129,8 +DA:130,574 +DA:131,1 +DA:132,573 +DA:133,573 +DA:134,2569 +DA:135,2008 +DA:136,12 +DA:137,1996 +DA:139,561 +DA:141,146 +DA:142,146 +DA:143,122 +DA:144,121 +DA:145,121 +DA:146,121 +DA:147,533 +DA:148,428 +DA:149,16 +DA:150,412 +DA:151,412 +DA:153,105 +DA:155,64 +DA:195,5673 +DA:196,5673 +DA:197,5673 +DA:200,26828 +DA:201,25812 +DA:202,25812 +DA:203,25812 +DA:204,25812 +DA:205,4657 +DA:206,21155 +DA:207,14137 +DA:209,7018 +DA:213,1016 +DA:216,9522 +DA:219,9522 +DA:221,12307 +DA:222,10096 +DA:223,3849 +DA:226,2211 +DA:231,706 +DA:232,448 +DA:233,558 +DA:234,475 +DA:235,285 +DA:236,146 +DA:239,44669 +DA:240,44669 +DA:244,45582 +DA:245,6241 +DA:246,6241 +DA:250,39341 +DA:251,7890 +DA:252,7890 +DA:255,31451 +DA:257,9522 +DA:258,32417 +DA:260,9522 +DA:261,9522 +DA:263,9522 +DA:264,1016 +DA:266,8506 +DA:268,3794 +DA:269,3794 +DA:270,3794 +DA:271,3794 +DA:273,21 +DA:275,21 +DA:277,4712 +DA:279,4712 +DA:280,4712 +DA:281,4712 +DA:287,21929 +DA:288,2240 +DA:289,855 +DA:290,710 +DA:291,519 +DA:292,722 +DA:293,462 +DA:294,7435 +DA:295,5973 +DA:296,2019 +DA:297,913 +DA:298,81 +DA:302,39341 +DA:310,23441 +DA:315,23441 +DA:316,7870 +DA:317,7870 +DA:318,7870 +DA:320,7870 +DA:321,7870 +DA:322,7870 +DA:324,3794 +DA:325,3794 +DA:326,3794 +DA:328,3794 +DA:329,3794 +DA:330,3794 +DA:332,125 +DA:334,125 +DA:335,125 +DA:337,125 +DA:338,125 +DA:339,125 +DA:340,125 +DA:341,7 +DA:342,7 +DA:343,2 +DA:345,5 +DA:348,125 +DA:350,2673 +DA:352,2673 +DA:353,2673 +DA:355,2673 +DA:356,2673 +DA:357,2673 +DA:358,2673 +DA:359,2673 +DA:360,2673 +DA:362,1880 +DA:366,1880 +DA:368,1880 +DA:369,1880 +DA:371,1880 +DA:372,1880 +DA:373,1880 +DA:375,1880 +DA:376,1 +DA:379,3746 +DA:380,3743 +DA:381,3743 +DA:382,3743 +DA:384,3743 +DA:385,1876 +DA:388,1879 +DA:389,75 +DA:391,1804 +DA:395,1880 +DA:397,5507 +DA:398,5507 +DA:399,5507 +DA:400,5507 +DA:402,5507 +DA:403,1526 +DA:405,3981 +DA:407,5507 +DA:409,1592 +DA:410,1592 +DA:411,1592 +DA:413,1592 +DA:414,1592 +DA:415,1592 +DA:418,23441 +DA:422,23441 +DA:424,23441 +DA:425,25203 +DA:426,1762 +DA:427,1762 +DA:432,23441 +DA:433,23128 +DA:435,313 +DA:436,313 +DA:438,313 +DA:439,313 +DA:441,313 +DA:444,23441 +DA:503,20079 +DA:505,20079 +DA:506,20079 +DA:508,20768 +DA:509,689 +DA:510,689 +DA:511,689 +DA:512,689 +DA:514,689 +DA:515,689 +DA:516,689 +DA:518,689 +DA:521,20079 +DA:527,18516 +DA:529,18516 +DA:530,18516 +DA:532,20079 +DA:533,1563 +DA:534,1563 +DA:535,1563 +DA:536,1563 +DA:538,1563 +DA:539,1563 +DA:540,1563 +DA:542,1563 +DA:545,18516 +DA:549,17183 +DA:551,17183 +DA:552,17183 +DA:554,18516 +DA:555,1333 +DA:556,1333 +DA:557,1333 +DA:558,1333 +DA:560,1333 +DA:561,1333 +DA:562,1333 +DA:564,1333 +DA:567,17183 +DA:571,13290 +DA:573,13290 +DA:574,13290 +DA:576,13440 +DA:577,150 +DA:578,150 +DA:579,150 +DA:581,150 +DA:582,150 +DA:583,150 +DA:585,150 +DA:588,13290 +DA:596,24650 +DA:597,24650 +DA:599,24650 +DA:600,12866 +DA:601,3830 +DA:603,7932 +DA:605,7932 +DA:606,106 +DA:607,2622 +DA:608,5193 +DA:609,3 +DA:610,2 +DA:611,2 +DA:612,2 +DA:613,2 +DA:614,0 +DA:617,22 +DA:619,22 +DA:620,4 +DA:621,10 +DA:622,3 +DA:623,1 +DA:624,1 +DA:625,1 +DA:626,1 +DA:627,1 +DA:628,0 +DA:631,0 +DA:639,18835 +DA:641,18835 +DA:642,11701 +DA:645,7911 +DA:646,7866 +DA:647,7866 +DA:649,20802 +DA:650,12936 +DA:651,12936 +DA:652,3815 +DA:655,7866 +DA:656,5630 +DA:657,5630 +DA:658,5630 +DA:659,5630 +DA:665,7783 +DA:667,7783 +DA:668,7783 +DA:669,7783 +DA:671,7783 +DA:672,7783 +DA:673,7783 +DA:674,0 +DA:675,0 +DA:678,7783 +DA:679,1884 +DA:680,1884 +DA:681,1870 +DA:682,1870 +DA:684,1884 +DA:686,5899 +DA:687,5899 +DA:688,5899 +DA:693,2800 +DA:694,2800 +DA:697,2800 +DA:698,1856 +DA:699,1856 +DA:701,944 +DA:703,2800 +DA:706,73 +DA:708,73 +DA:710,73 +DA:711,56 +DA:712,2 +DA:714,15 +DA:718,15 +DA:719,15 +DA:720,70 +DA:721,55 +DA:723,15 +DA:724,70 +DA:725,55 +DA:727,15 +DA:729,73 +DA:732,18 +DA:733,18 +DA:734,18 +LF:336 +LH:331 +end_of_record diff --git a/docs/evidence/coverage-c-upstream.info b/docs/evidence/coverage-c-upstream.info new file mode 100644 index 0000000..5a5d074 --- /dev/null +++ b/docs/evidence/coverage-c-upstream.info @@ -0,0 +1,674 @@ +TN: +SF:/mnt/d/hackathons/portmortem/tinyexpr.zig/third_party/tinyexpr-c/tinyexpr.c +FN:104,115,te_free_parameters +FN:118,122,te_free +FN:125,125,pi +FN:126,126,e +FN:127,140,fac +FN:141,154,ncr +FN:155,155,npr +FN:195,214,find_builtin +FN:216,227,find_lookup +FN:231,231,add +FN:232,232,sub +FN:233,233,mul +FN:234,234,divide +FN:235,235,negate +FN:236,236,comma +FN:239,303,next_token +FN:310,419,base +FN:422,445,power +FN:503,522,factor +FN:527,546,term +FN:549,568,expr +FN:571,589,list +FN:596,634,te_eval +FN:639,662,optimize +FN:665,690,te_compile +FN:693,704,te_interp +FN:706,729,pn +FN:732,734,te_print +FN:87,101,new_expr +FNDA:7111,te_free_parameters +FNDA:6827,te_free +FNDA:3,pi +FNDA:2,e +FNDA:29,fac +FNDA:39,ncr +FNDA:18,npr +FNDA:2323,find_builtin +FNDA:6150,find_lookup +FNDA:63,add +FNDA:14,sub +FNDA:30,mul +FNDA:15,divide +FNDA:45,negate +FNDA:12,comma +FNDA:14325,next_token +FNDA:6685,base +FNDA:6685,power +FNDA:5927,factor +FNDA:5864,term +FNDA:5804,expr +FNDA:2540,list +FNDA:7112,te_eval +FNDA:6696,optimize +FNDA:2455,te_compile +FNDA:148,te_interp +FNDA:0,pn +FNDA:0,te_print +FNDA:6827,new_expr +FNF:29 +FNH:27 +BRDA:88,0,0,2575 +BRDA:88,0,1,4252 +BRDA:92,0,0,0 +BRDA:92,0,1,6827 +BRDA:95,0,0,2556 +BRDA:95,0,1,4271 +BRDA:95,0,2,227 +BRDA:95,0,3,2329 +BRDA:105,0,0,0 +BRDA:105,0,1,7111 +BRDA:106,0,0,1 +BRDA:106,0,1,1 +BRDA:106,0,2,1 +BRDA:106,0,3,1 +BRDA:106,0,4,2 +BRDA:106,0,5,1794 +BRDA:106,0,6,756 +BRDA:106,0,7,4555 +BRDA:119,0,0,0 +BRDA:119,0,1,6827 +BRDA:128,0,0,4 +BRDA:128,0,1,25 +BRDA:130,0,0,0 +BRDA:130,0,1,25 +BRDA:134,0,0,186 +BRDA:134,0,1,19 +BRDA:135,0,0,6 +BRDA:135,0,1,180 +BRDA:142,0,0,35 +BRDA:142,0,1,4 +BRDA:142,0,2,31 +BRDA:142,0,3,4 +BRDA:142,0,4,4 +BRDA:142,0,5,27 +BRDA:143,0,0,27 +BRDA:143,0,1,0 +BRDA:143,0,2,0 +BRDA:143,0,3,27 +BRDA:146,0,0,10 +BRDA:146,0,1,17 +BRDA:147,0,0,110 +BRDA:147,0,1,19 +BRDA:148,0,0,8 +BRDA:148,0,1,102 +BRDA:200,0,0,10211 +BRDA:200,0,1,10 +BRDA:203,0,0,2387 +BRDA:203,0,1,7824 +BRDA:204,0,0,2313 +BRDA:204,0,1,7898 +BRDA:206,0,0,4404 +BRDA:206,0,1,3494 +BRDA:219,0,0,114 +BRDA:219,0,1,6036 +BRDA:221,0,0,9929 +BRDA:221,0,1,2209 +BRDA:222,0,0,3827 +BRDA:222,0,1,6102 +BRDA:222,0,2,3827 +BRDA:222,0,3,0 +BRDA:244,0,0,2418 +BRDA:244,0,1,12635 +BRDA:250,0,0,6614 +BRDA:250,0,1,6021 +BRDA:250,0,2,6203 +BRDA:250,0,3,411 +BRDA:250,0,4,15 +BRDA:250,0,5,12209 +BRDA:255,0,0,6150 +BRDA:255,0,1,6059 +BRDA:258,0,0,12274 +BRDA:258,0,1,7283 +BRDA:258,0,2,1132 +BRDA:258,0,3,6151 +BRDA:258,0,4,1 +BRDA:258,0,5,6150 +BRDA:261,0,0,2323 +BRDA:261,0,1,3827 +BRDA:263,0,0,10 +BRDA:263,0,1,6140 +BRDA:266,0,0,3792 +BRDA:266,0,1,10 +BRDA:266,0,2,2338 +BRDA:266,0,3,0 +BRDA:287,0,0,61 +BRDA:287,0,1,79 +BRDA:287,0,2,44 +BRDA:287,0,3,15 +BRDA:287,0,4,50 +BRDA:287,0,5,10 +BRDA:287,0,6,1713 +BRDA:287,0,7,1704 +BRDA:287,0,8,1646 +BRDA:287,0,9,728 +BRDA:287,0,10,9 +BRDA:302,0,0,728 +BRDA:302,0,1,11907 +BRDA:315,0,0,426 +BRDA:315,0,1,3792 +BRDA:315,0,2,19 +BRDA:315,0,3,711 +BRDA:315,0,4,1618 +BRDA:315,0,5,85 +BRDA:315,0,6,34 +BRDA:318,0,0,0 +BRDA:318,0,1,426 +BRDA:326,0,0,0 +BRDA:326,0,1,3792 +BRDA:335,0,0,0 +BRDA:335,0,1,19 +BRDA:338,0,0,1 +BRDA:338,0,1,18 +BRDA:340,0,0,4 +BRDA:340,0,1,15 +BRDA:342,0,0,0 +BRDA:342,0,1,4 +BRDA:353,0,0,0 +BRDA:353,0,1,711 +BRDA:356,0,0,8 +BRDA:356,0,1,703 +BRDA:359,0,0,0 +BRDA:359,0,1,711 +BRDA:366,0,0,1618 +BRDA:366,0,1,0 +BRDA:369,0,0,0 +BRDA:369,0,1,1618 +BRDA:372,0,0,1 +BRDA:372,0,1,1617 +BRDA:375,0,0,0 +BRDA:375,0,1,1618 +BRDA:379,0,0,3252 +BRDA:379,0,1,0 +BRDA:382,0,0,0 +BRDA:382,0,1,3252 +BRDA:384,0,0,1618 +BRDA:384,0,1,1634 +BRDA:388,0,0,1618 +BRDA:388,0,1,0 +BRDA:388,0,2,0 +BRDA:388,0,3,1618 +BRDA:400,0,0,0 +BRDA:400,0,1,85 +BRDA:402,0,0,6 +BRDA:402,0,1,79 +BRDA:411,0,0,0 +BRDA:411,0,1,34 +BRDA:425,0,0,89 +BRDA:425,0,1,6676 +BRDA:425,0,2,11 +BRDA:425,0,3,78 +BRDA:425,0,4,69 +BRDA:425,0,5,9 +BRDA:426,0,0,69 +BRDA:426,0,1,11 +BRDA:432,0,0,6640 +BRDA:432,0,1,45 +BRDA:436,0,0,0 +BRDA:436,0,1,45 +BRDA:439,0,0,0 +BRDA:439,0,1,45 +BRDA:506,0,0,0 +BRDA:506,0,1,5927 +BRDA:508,0,0,170 +BRDA:508,0,1,5804 +BRDA:508,0,2,47 +BRDA:508,0,3,123 +BRDA:512,0,0,0 +BRDA:512,0,1,47 +BRDA:516,0,0,0 +BRDA:516,0,1,47 +BRDA:530,0,0,0 +BRDA:530,0,1,5864 +BRDA:532,0,0,123 +BRDA:532,0,1,5804 +BRDA:532,0,2,38 +BRDA:532,0,3,85 +BRDA:532,0,4,15 +BRDA:532,0,5,70 +BRDA:532,0,6,10 +BRDA:532,0,7,60 +BRDA:536,0,0,0 +BRDA:536,0,1,63 +BRDA:540,0,0,0 +BRDA:540,0,1,63 +BRDA:552,0,0,0 +BRDA:552,0,1,5804 +BRDA:554,0,0,60 +BRDA:554,0,1,5804 +BRDA:554,0,2,50 +BRDA:554,0,3,10 +BRDA:554,0,4,10 +BRDA:554,0,5,0 +BRDA:558,0,0,0 +BRDA:558,0,1,60 +BRDA:562,0,0,0 +BRDA:562,0,1,60 +BRDA:574,0,0,0 +BRDA:574,0,1,2540 +BRDA:576,0,0,12 +BRDA:576,0,1,2540 +BRDA:579,0,0,0 +BRDA:579,0,1,12 +BRDA:583,0,0,0 +BRDA:583,0,1,12 +BRDA:597,0,0,0 +BRDA:597,0,1,7112 +BRDA:599,0,0,691 +BRDA:599,0,1,3828 +BRDA:599,0,2,2580 +BRDA:599,0,3,13 +BRDA:599,0,4,0 +BRDA:605,0,0,2580 +BRDA:605,0,1,0 +BRDA:605,0,2,18 +BRDA:605,0,3,753 +BRDA:605,0,4,1803 +BRDA:605,0,5,2 +BRDA:605,0,6,1 +BRDA:605,0,7,1 +BRDA:605,0,8,1 +BRDA:605,0,9,1 +BRDA:605,0,10,0 +BRDA:619,0,0,13 +BRDA:619,0,1,0 +BRDA:619,0,2,2 +BRDA:619,0,3,9 +BRDA:619,0,4,2 +BRDA:619,0,5,0 +BRDA:619,0,6,0 +BRDA:619,0,7,0 +BRDA:619,0,8,0 +BRDA:619,0,9,0 +BRDA:619,0,10,0 +BRDA:641,0,0,358 +BRDA:641,0,1,6338 +BRDA:642,0,0,3788 +BRDA:642,0,1,2550 +BRDA:645,0,0,2519 +BRDA:645,0,1,31 +BRDA:646,0,0,2519 +BRDA:646,0,1,0 +BRDA:649,0,0,4287 +BRDA:649,0,1,2519 +BRDA:651,0,0,3814 +BRDA:651,0,1,473 +BRDA:655,0,0,284 +BRDA:655,0,1,2235 +BRDA:673,0,0,0 +BRDA:673,0,1,2455 +BRDA:674,0,0,- +BRDA:674,0,1,- +BRDA:678,0,0,46 +BRDA:678,0,1,2409 +BRDA:680,0,0,32 +BRDA:680,0,1,14 +BRDA:682,0,0,2 +BRDA:682,0,1,30 +BRDA:687,0,0,2381 +BRDA:687,0,1,28 +BRDA:697,0,0,120 +BRDA:697,0,1,28 +BRDA:710,0,0,- +BRDA:710,0,1,- +BRDA:710,0,2,- +BRDA:710,0,3,- +BRDA:718,0,0,- +BRDA:718,0,1,- +BRDA:720,0,0,- +BRDA:720,0,1,- +BRDA:724,0,0,- +BRDA:724,0,1,- +BRF:271 +BRH:208 +DA:87,6827 +DA:88,6827 +DA:89,6827 +DA:90,6827 +DA:91,6827 +DA:92,6827 +DA:94,6827 +DA:95,6827 +DA:96,227 +DA:98,6827 +DA:99,6827 +DA:100,6827 +DA:104,7111 +DA:105,7111 +DA:106,7111 +DA:107,1 +DA:108,2 +DA:109,3 +DA:110,4 +DA:111,6 +DA:112,1800 +DA:113,2556 +DA:118,6827 +DA:119,6827 +DA:120,6827 +DA:121,6827 +DA:125,3 +DA:126,2 +DA:127,29 +DA:128,29 +DA:129,4 +DA:130,25 +DA:131,0 +DA:132,25 +DA:133,25 +DA:134,205 +DA:135,186 +DA:136,6 +DA:137,180 +DA:139,19 +DA:141,39 +DA:142,39 +DA:143,27 +DA:144,27 +DA:145,27 +DA:146,27 +DA:147,129 +DA:148,110 +DA:149,8 +DA:150,102 +DA:151,102 +DA:153,19 +DA:155,18 +DA:195,2323 +DA:196,2323 +DA:197,2323 +DA:200,10221 +DA:201,10211 +DA:202,10211 +DA:203,10211 +DA:204,10211 +DA:205,2313 +DA:206,7898 +DA:207,4404 +DA:209,3494 +DA:213,10 +DA:216,6150 +DA:219,6150 +DA:221,12138 +DA:222,9929 +DA:223,3827 +DA:226,2209 +DA:231,63 +DA:232,14 +DA:233,30 +DA:234,15 +DA:235,45 +DA:236,12 +DA:239,14325 +DA:240,14325 +DA:244,15053 +DA:245,2418 +DA:246,2418 +DA:250,12635 +DA:251,426 +DA:252,426 +DA:255,12209 +DA:257,6150 +DA:258,19557 +DA:260,6150 +DA:261,6150 +DA:263,6150 +DA:264,10 +DA:266,6140 +DA:268,3792 +DA:269,3792 +DA:270,3792 +DA:271,3792 +DA:273,10 +DA:275,10 +DA:277,2348 +DA:279,2348 +DA:280,2348 +DA:281,2348 +DA:287,6059 +DA:288,61 +DA:289,79 +DA:290,44 +DA:291,15 +DA:292,50 +DA:293,10 +DA:294,1713 +DA:295,1704 +DA:296,1646 +DA:297,728 +DA:298,9 +DA:302,12635 +DA:310,6685 +DA:315,6685 +DA:316,426 +DA:317,426 +DA:318,426 +DA:320,426 +DA:321,426 +DA:322,426 +DA:324,3792 +DA:325,3792 +DA:326,3792 +DA:328,3792 +DA:329,3792 +DA:330,3792 +DA:332,19 +DA:334,19 +DA:335,19 +DA:337,19 +DA:338,19 +DA:339,19 +DA:340,19 +DA:341,4 +DA:342,4 +DA:343,0 +DA:345,4 +DA:348,19 +DA:350,711 +DA:352,711 +DA:353,711 +DA:355,711 +DA:356,711 +DA:357,711 +DA:358,711 +DA:359,711 +DA:360,711 +DA:362,1618 +DA:366,1618 +DA:368,1618 +DA:369,1618 +DA:371,1618 +DA:372,1618 +DA:373,1618 +DA:375,1618 +DA:376,0 +DA:379,3252 +DA:380,3252 +DA:381,3252 +DA:382,3252 +DA:384,3252 +DA:385,1618 +DA:388,1618 +DA:389,0 +DA:391,1618 +DA:395,1618 +DA:397,85 +DA:398,85 +DA:399,85 +DA:400,85 +DA:402,85 +DA:403,6 +DA:405,79 +DA:407,85 +DA:409,34 +DA:410,34 +DA:411,34 +DA:413,34 +DA:414,34 +DA:415,34 +DA:418,6685 +DA:422,6685 +DA:424,6685 +DA:425,6765 +DA:426,80 +DA:427,80 +DA:432,6685 +DA:433,6640 +DA:435,45 +DA:436,45 +DA:438,45 +DA:439,45 +DA:441,45 +DA:444,6685 +DA:503,5927 +DA:505,5927 +DA:506,5927 +DA:508,5974 +DA:509,47 +DA:510,47 +DA:511,47 +DA:512,47 +DA:514,47 +DA:515,47 +DA:516,47 +DA:518,47 +DA:521,5927 +DA:527,5864 +DA:529,5864 +DA:530,5864 +DA:532,5927 +DA:533,63 +DA:534,63 +DA:535,63 +DA:536,63 +DA:538,63 +DA:539,63 +DA:540,63 +DA:542,63 +DA:545,5864 +DA:549,5804 +DA:551,5804 +DA:552,5804 +DA:554,5864 +DA:555,60 +DA:556,60 +DA:557,60 +DA:558,60 +DA:560,60 +DA:561,60 +DA:562,60 +DA:564,60 +DA:567,5804 +DA:571,2540 +DA:573,2540 +DA:574,2540 +DA:576,2552 +DA:577,12 +DA:578,12 +DA:579,12 +DA:581,12 +DA:582,12 +DA:583,12 +DA:585,12 +DA:588,2540 +DA:596,7112 +DA:597,7112 +DA:599,7112 +DA:600,691 +DA:601,3828 +DA:603,2580 +DA:605,2580 +DA:606,18 +DA:607,753 +DA:608,1803 +DA:609,2 +DA:610,1 +DA:611,1 +DA:612,1 +DA:613,1 +DA:614,0 +DA:617,13 +DA:619,13 +DA:620,2 +DA:621,9 +DA:622,2 +DA:623,0 +DA:624,0 +DA:625,0 +DA:626,0 +DA:627,0 +DA:628,0 +DA:631,0 +DA:639,6696 +DA:641,6696 +DA:642,6338 +DA:645,2550 +DA:646,2519 +DA:647,2519 +DA:649,6806 +DA:650,4287 +DA:651,4287 +DA:652,3814 +DA:655,2519 +DA:656,284 +DA:657,284 +DA:658,284 +DA:659,284 +DA:665,2455 +DA:667,2455 +DA:668,2455 +DA:669,2455 +DA:671,2455 +DA:672,2455 +DA:673,2455 +DA:674,0 +DA:675,0 +DA:678,2455 +DA:679,46 +DA:680,46 +DA:681,32 +DA:682,32 +DA:684,46 +DA:686,2409 +DA:687,2409 +DA:688,2409 +DA:693,148 +DA:694,148 +DA:697,148 +DA:698,120 +DA:699,120 +DA:701,28 +DA:703,148 +DA:706,0 +DA:708,0 +DA:710,0 +DA:711,0 +DA:712,0 +DA:714,0 +DA:718,0 +DA:719,0 +DA:720,0 +DA:721,0 +DA:723,0 +DA:724,0 +DA:725,0 +DA:727,0 +DA:729,0 +DA:732,0 +DA:733,0 +DA:734,0 +LF:336 +LH:304 +end_of_record diff --git a/docs/evidence/coverage-c.log b/docs/evidence/coverage-c.log new file mode 100644 index 0000000..7159fb1 --- /dev/null +++ b/docs/evidence/coverage-c.log @@ -0,0 +1,178 @@ +=== C coverage metadata === +started_at_utc=2026-08-02T03:42:07Z +git_sha=33837ec06aecb68f23190594923c3fb52400fb37 +host=Linux elmengo 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 18 21:54:43 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux +gcc_version=gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 +Copyright (C) 2023 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +lcov_version=lcov: LCOV version 2.0-1 +command_line=/mnt/d/hackathons/portmortem/tinyexpr.zig/scripts/run-coverage-c.sh +cflags=--coverage -fprofile-arcs -ftest-coverage -O0 -I/mnt/d/hackathons/portmortem/tinyexpr.zig/third_party/tinyexpr-c +=== upstream-suite-only baseline === + Results pass:158 fail: 0 0ms + Syntax pass:70 fail: 0 0ms + NaNs pass:55 fail: 0 0ms + INFs pass:50 fail: 0 0ms + Variables pass:36 fail: 0 0ms + Functions pass:4406 fail: 0 1ms + Dynamic pass:44 fail: 0 0ms + Closure pass:17 fail: 0 0ms + Optimize pass:12 fail: 0 0ms + Pow pass:42 fail: 0 0ms + Combinatorics pass:40 fail: 0 0ms +ALL TESTS PASSED (4930/4930) +Capturing coverage data from /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream +geninfo cmd: '/home/aadi/.local/lcov/usr/bin/geninfo /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream --output-filename /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream/all.info --base-directory /mnt/d/hackathons/portmortem/tinyexpr.zig --rc branch_coverage=1 --memory 0 --branch-coverage' +Found gcov version: 13.3.0 +Using intermediate gcov format +Writing temporary data to /tmp/geninfo_dataxB3 +Scanning /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream for .gcda files ... +Found 2 data files in /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream +Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream/smoke.gcda +geninfo: WARNING: using JSON module "JSON::PP" - which is much slower than some alternatives. Consider installing one of JSON::XS or Cpanel::JSON::XS +Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream/tinyexpr.gcda +Finished .info-file creation +Excluding /mnt/d/hackathons/portmortem/tinyexpr.zig/tests/original/smoke.c +Removed 1 files +Writing data to /mnt/d/hackathons/portmortem/tinyexpr.zig/docs/evidence/coverage-c-upstream.info +Summary coverage rate: + lines......: 90.5% (304 of 336 lines) + functions..: 93.1% (27 of 29 functions) + branches...: 76.8% (208 of 271 branches) +Summary coverage rate: + lines......: 90.5% (304 of 336 lines) + functions..: 93.1% (27 of 29 functions) + branches...: 76.8% (208 of 271 branches) +Found 1 entries. +Using user-specified filename prefix "/mnt/d/hackathons/portmortem/tinyexpr.zig" +Generating output. +Processing file third_party/tinyexpr-c/tinyexpr.c + lines=336 hit=304 functions=29 hit=27 branches=271 hit=208 +Overall coverage rate: + lines......: 90.5% (304 of 336 lines) + functions......: 93.1% (27 of 29 functions) + branches......: 76.8% (208 of 271 branches) +=== expanded corpus (upstream suite + fixtures/seeds) === + Results pass:158 fail: 0 0ms + Syntax pass:70 fail: 0 0ms + NaNs pass:55 fail: 0 0ms + INFs pass:50 fail: 0 0ms + Variables pass:36 fail: 0 0ms + Functions pass:4406 fail: 0 1ms + Dynamic pass:44 fail: 0 0ms + Closure pass:17 fail: 0 0ms + Optimize pass:12 fail: 0 0ms + Pow pass:42 fail: 0 0ms + Combinatorics pass:40 fail: 0 0ms +ALL TESTS PASSED (4930/4930) +expanded_corpus_expressions=2652 +coverage driver: source=/mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded-input.txt total=2652 interp_ok=1736 compile_ok=1736 +f3 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 + 1.000000 + 2.000000 + 3.000000 +f4 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 + 1.000000 + 2.000000 + 3.000000 + 4.000000 +f5 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 +f6 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 + 6.000000 +f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 0x620ab76c4430 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 + 6.000000 + 7.000000 +f0 +f0 +f1 0x620ab76c32d0 + 9.000000 +f2 0x620ab76c32d0 0x620ab76c32f0 + 1.000000 + 2.000000 +f3 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 + 1.000000 + 2.000000 + 3.000000 +f4 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 + 1.000000 + 2.000000 + 3.000000 + 4.000000 +f5 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 +f6 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 + 6.000000 +f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 0x620ab76c4430 + 1.000000 + 2.000000 + 3.000000 + 4.000000 + 5.000000 + 6.000000 + 7.000000 +bound 0x620aac8e9aa0 +f2 0x620ab76c32d0 0x620ab76c32f0 + bound 0x620aac8e9aa0 + 1.000000 +inf +inf +Capturing coverage data from /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded +geninfo cmd: '/home/aadi/.local/lcov/usr/bin/geninfo /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded --output-filename /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/all.info --base-directory /mnt/d/hackathons/portmortem/tinyexpr.zig --rc branch_coverage=1 --memory 0 --branch-coverage' +Found gcov version: 13.3.0 +Using intermediate gcov format +Writing temporary data to /tmp/geninfo_datRccJ +Scanning /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded for .gcda files ... +Found 3 data files in /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded +Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/coverage_driver.gcda +geninfo: WARNING: using JSON module "JSON::PP" - which is much slower than some alternatives. Consider installing one of JSON::XS or Cpanel::JSON::XS +Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/smoke.gcda +Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/tinyexpr.gcda +Finished .info-file creation +Excluding /mnt/d/hackathons/portmortem/tinyexpr.zig/tests/original/smoke.c +Excluding /mnt/d/hackathons/portmortem/tinyexpr.zig/tools/coverage_driver.c +Removed 2 files +Writing data to /mnt/d/hackathons/portmortem/tinyexpr.zig/docs/evidence/coverage-c-expanded.info +Summary coverage rate: + lines......: 98.5% (331 of 336 lines) + functions..: 100.0% (29 of 29 functions) + branches...: 84.5% (229 of 271 branches) +Summary coverage rate: + lines......: 98.5% (331 of 336 lines) + functions..: 100.0% (29 of 29 functions) + branches...: 84.5% (229 of 271 branches) +Found 1 entries. +Using user-specified filename prefix "/mnt/d/hackathons/portmortem/tinyexpr.zig" +Generating output. +Processing file third_party/tinyexpr-c/tinyexpr.c + lines=336 hit=331 functions=29 hit=29 branches=271 hit=229 +Overall coverage rate: + lines......: 98.5% (331 of 336 lines) + functions......: 100.0% (29 of 29 functions) + branches......: 84.5% (229 of 271 branches) +completed_at_utc=2026-08-02T03:42:27Z +coverage evidence written under docs/evidence/ diff --git a/docs/evidence/coverage.md b/docs/evidence/coverage.md new file mode 100644 index 0000000..eaecffa --- /dev/null +++ b/docs/evidence/coverage.md @@ -0,0 +1,53 @@ +# C original coverage + +This records coverage of the vendored original at +`third_party/tinyexpr-c/tinyexpr.c`; neither that source nor +`tests/original/` was modified. The collection command was: + +```text +bash scripts/run-coverage-c.sh +``` + +The run used GCC 13.3.0 and lcov 2.0-1 on WSL Ubuntu. Its source revision was +`33837ec06aecb68f23190594923c3fb52400fb37`, from +2026-08-02T03:42:07Z to 2026-08-02T03:42:27Z. + +## Upstream-suite-only baseline + +The unmodified default `smoke.c` suite passed its required +`4930/4930` assertions while instrumented. + +- Lines: 304 / 336 (90.48%) +- Functions: 27 / 29 (93.10%) +- Branches: 208 / 271 (76.75%) + +Tracefile: `docs/evidence/coverage-c-upstream.info`. + +## Expanded corpus + +This mode first runs the same unmodified smoke suite, then invokes both +`te_interp` and `te_compile` for each of 2,652 expressions assembled from all +`tests/port/fixtures/*.jsonl` expression fields, `fuzz/corpus-seed/*`, and +string literals extracted from `smoke.c`. + +- Lines: 331 / 336 (98.51%) +- Functions: 29 / 29 (100.00%) +- Branches: 229 / 271 (84.50%) + +Tracefile: `docs/evidence/coverage-c-expanded.info`. + +## Expanded unreached lines + +The five zero-hit executable lines in the expanded trace are defensive or +invalid-state paths: + +- 614 and 628: default arms of `te_eval`'s function and closure arity + switches. Public parsing creates only arities 0 through 7. +- 631: `te_eval`'s outer default for an invalid expression type. Reaching it + requires a manually corrupted `te_expr`. +- 674–675: `te_compile`'s `list()` allocation-failure path. Normal corpus + execution does not inject allocation failures. + +The raw lcov counts, environment metadata, command line, and UTC timestamps +are in `docs/evidence/fragments/coverage-c.json`. HTML is generated under +`docs/evidence/coverage-c-html/` and intentionally ignored as derived output. diff --git a/docs/evidence/fragments/coverage-c.json b/docs/evidence/fragments/coverage-c.json new file mode 100644 index 0000000..49d9d10 --- /dev/null +++ b/docs/evidence/fragments/coverage-c.json @@ -0,0 +1,58 @@ +{ + "schema_version": 1, + "subject": "third_party/tinyexpr-c/tinyexpr.c", + "coverage_tool": "lcov", + "command_line": "/mnt/d/hackathons/portmortem/tinyexpr.zig/scripts/run-coverage-c.sh", + "git_sha": "33837ec06aecb68f23190594923c3fb52400fb37", + "host": "Linux elmengo 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 18 21:54:43 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux", + "compiler": { + "gcc": "gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0\nCopyright (C) 2023 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.", + "lcov": "lcov: LCOV version 2.0-1" + }, + "started_at_utc": "2026-08-02T03:42:07Z", + "completed_at_utc": "2026-08-02T03:42:27Z", + "modes": { + "upstream_suite_only": { + "info": "docs/evidence/coverage-c-upstream.info", + "html": "docs/evidence/coverage-c-html/upstream/", + "counts": { + "lines": { + "hit": 304, + "found": 336, + "percent": 90.48 + }, + "functions": { + "hit": 27, + "found": 29, + "percent": 93.1 + }, + "branches": { + "hit": 208, + "found": 271, + "percent": 76.75 + } + } + }, + "expanded_corpus": { + "info": "docs/evidence/coverage-c-expanded.info", + "html": "docs/evidence/coverage-c-html/expanded/", + "counts": { + "lines": { + "hit": 331, + "found": 336, + "percent": 98.51 + }, + "functions": { + "hit": 29, + "found": 29, + "percent": 100.0 + }, + "branches": { + "hit": 229, + "found": 271, + "percent": 84.5 + } + } + } + } +} diff --git a/scripts/run-coverage-c.sh b/scripts/run-coverage-c.sh index 3db092d..43d6d13 100644 --- a/scripts/run-coverage-c.sh +++ b/scripts/run-coverage-c.sh @@ -29,12 +29,44 @@ CFLAGS=(--coverage -fprofile-arcs -ftest-coverage -O0 -I"$INCLUDE") LCOV="" GENHTML="" LOCAL_LCOV_PREFIX="$HOME/.local/lcov" +patch_local_lcov_paths() { + python3 - "$LOCAL_LCOV_PREFIX" <<'PY' +import sys +from pathlib import Path + +prefix = Path(sys.argv[1]) +for path in ( + prefix / "usr/bin/lcov", + prefix / "usr/bin/genhtml", + prefix / "usr/bin/geninfo", + prefix / "usr/lib/lcov/lcovutil.pm", +): + if not path.exists(): + continue + lines = path.read_text(encoding="utf-8").splitlines(keepends=True) + if lines and lines[0].startswith("#!") and "perl" in lines[0]: + lines[0] = "#!/usr/bin/perl\n" + content = "".join(lines) + # Tool lookup uses the quoted "/usr/bin" directory token inside lcovutil. + content = content.replace('"/usr/bin"', f'"{prefix / "usr/bin"}"') + path.write_text(content, encoding="utf-8") +PY +} + use_local_lcov() { LCOV="$LOCAL_LCOV_PREFIX/usr/bin/lcov" GENHTML="$LOCAL_LCOV_PREFIX/usr/bin/genhtml" - local perl_vendor_arch - perl_vendor_arch="$(perl -MConfig -e 'print $Config{vendorarch}')" - export PERL5LIB="$LOCAL_LCOV_PREFIX/usr/lib/lcov:$LOCAL_LCOV_PREFIX/usr/share/perl5:$LOCAL_LCOV_PREFIX$perl_vendor_arch${PERL5LIB:+:$PERL5LIB}" + local paths=("$LOCAL_LCOV_PREFIX/usr/lib/lcov" "$LOCAL_LCOV_PREFIX/usr/share/perl5") + local dir + shopt -s nullglob + for dir in "$LOCAL_LCOV_PREFIX"/usr/lib/*/perl5/*; do + paths+=("$dir") + done + shopt -u nullglob + local joined + joined="$(IFS=:; echo "${paths[*]}")" + export PERL5LIB="$joined${PERL5LIB:+:$PERL5LIB}" + patch_local_lcov_paths "$LCOV" --version >/dev/null 2>&1 && "$GENHTML" --version >/dev/null 2>&1 } @@ -54,17 +86,24 @@ setup_lcov() { fi fi - echo "lcov/genhtml not found; extracting lcov and its Perl dependencies to $prefix" + echo "lcov/genhtml not found; extracting only the lcov package to $prefix" local download_dir download_dir="$(mktemp -d)" if ( cd "$download_dir" - apt-cache depends --recurse --no-recommends lcov | - awk '/^[[:space:]]*[|]?(PreDepends|Depends): / { print $2 }' | - awk '!/^ packages.txt - mapfile -t packages < packages.txt - apt-get download lcov "${packages[@]}" + # Prefer a tiny footprint: lcov's packaging usually ships genhtml with it. + apt-get download lcov libcapture-tiny-perl libdatetime-perl libdatetime-locale-perl \ + libdatetime-timezone-perl libparams-validate-perl libtimedate-perl \ + libnamespace-autoclean-perl libparams-validationcompiler-perl libspecio-perl \ + libtry-tiny-perl libmodule-runtime-perl libmodule-implementation-perl \ + libsub-identify-perl libsub-name-perl libb-hooks-endofscope-perl \ + libvariable-magic-perl libpackage-stash-perl libpackage-stash-xs-perl \ + libnamespace-clean-perl libdevel-stacktrace-perl libclass-data-inheritable-perl \ + libexception-class-perl libeval-closure-perl librole-tiny-perl libsub-quote-perl \ + libmro-compat-perl libclass-method-modifiers-perl libclass-xsaccessor-perl \ + libfile-sharedir-perl libparams-util-perl libref-util-perl libref-util-xs-perl \ + libxstring-perl 2>/dev/null \ + || apt-get download lcov shopt -s nullglob packages=(*.deb) [[ ${#packages[@]} -gt 0 ]] @@ -194,11 +233,20 @@ build_mode upstream "$WORK/upstream/smoke" capture_coverage upstream "$UPSTREAM_INFO" "tinyexpr C upstream-suite-only coverage" -echo "=== expanded corpus ===" -build_mode expanded +echo "=== expanded corpus (upstream suite + fixtures/seeds) ===" +# Rebuild instrumented object once, then accumulate gcda from smoke AND the +# fixture driver so expanded coverage is a strict superset of the baseline. +rm -rf "$WORK/expanded" +mkdir -p "$WORK/expanded" +gcc "${CFLAGS[@]}" -c "$SOURCE" -o "$WORK/expanded/tinyexpr.o" +gcc "${CFLAGS[@]}" -c "$SMOKE" -o "$WORK/expanded/smoke.o" +gcc --coverage -O0 "$WORK/expanded/tinyexpr.o" "$WORK/expanded/smoke.o" -lm -o "$WORK/expanded/smoke" +"$WORK/expanded/smoke" CORPUS="$WORK/expanded-input.txt" CORPUS_COUNT="$(make_expanded_corpus "$CORPUS")" echo "expanded_corpus_expressions=$CORPUS_COUNT" +gcc "${CFLAGS[@]}" -c "$DRIVER" -o "$WORK/expanded/coverage_driver.o" +gcc --coverage -O0 "$WORK/expanded/tinyexpr.o" "$WORK/expanded/coverage_driver.o" -lm -o "$WORK/expanded/coverage_driver" "$WORK/expanded/coverage_driver" --file "$CORPUS" capture_coverage expanded "$EXPANDED_INFO" "tinyexpr C expanded-corpus coverage" @@ -263,27 +311,65 @@ result = { Path(fragment).write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") PY else - python3 - "$FRAGMENT" "$STARTED_AT" "$COMPLETED_AT" "$GIT_SHA" "$HOST" "$GCC_VERSION" "$COMMAND_LINE" <<'PY' + python3 - "$FRAGMENT" "$STARTED_AT" "$COMPLETED_AT" "$GIT_SHA" "$HOST" "$GCC_VERSION" "$COMMAND_LINE" \ + "$EVIDENCE/coverage-c-upstream.gcov.txt" "$EVIDENCE/coverage-c-expanded.gcov.txt" "$CORPUS_COUNT" <<'PY' import json +import re import sys from pathlib import Path -fragment, started, completed, sha, host, gcc, command = sys.argv[1:] +( + fragment, + started, + completed, + sha, + host, + gcc, + command, + upstream_report, + expanded_report, + corpus_count, +) = sys.argv[1:] + +def parse_gcov_summary(path): + text = Path(path).read_text(encoding="utf-8", errors="replace") + def pct(label): + match = re.search(rf"{label}:([0-9.]+)% of ([0-9]+)", text) + if not match: + raise SystemExit(f"missing {label} in {path}") + percent = float(match.group(1)) + found = int(match.group(2)) + hit = int(round(percent * found / 100.0)) + return {"hit": hit, "found": found, "percent": percent} + return { + "lines": pct("Lines executed"), + "branches_executed": pct("Branches executed"), + "branches_taken": pct("Taken at least once"), + "calls": pct("Calls executed"), + } + result = { "schema_version": 1, "subject": "third_party/tinyexpr-c/tinyexpr.c", - "coverage_tool": "gcov_fallback", - "status": "lcov_install_failed", + "coverage_tool": "gcov -b -c", + "status": "lcov_unavailable_gcov_fallback", "command_line": command, "git_sha": sha, "host": host, "compiler": {"gcc": gcc}, "started_at_utc": started, "completed_at_utc": completed, - "reports": [ - "docs/evidence/coverage-c-upstream.gcov.txt", - "docs/evidence/coverage-c-expanded.gcov.txt", - ], + "expanded_corpus_expressions": int(corpus_count), + "modes": { + "upstream_suite_only": { + "report": "docs/evidence/coverage-c-upstream.gcov.txt", + "counts": parse_gcov_summary(upstream_report), + }, + "expanded_corpus": { + "report": "docs/evidence/coverage-c-expanded.gcov.txt", + "counts": parse_gcov_summary(expanded_report), + }, + }, } Path(fragment).write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") PY diff --git a/tools/coverage_driver.c b/tools/coverage_driver.c index 04e525a..126284a 100644 --- a/tools/coverage_driver.c +++ b/tools/coverage_driver.c @@ -1,12 +1,56 @@ -/* Coverage harness for tinyexpr-c: execute both public parse entry points. */ +/* Coverage harness for tinyexpr-c: drive public API and high-arity paths. */ #define _POSIX_C_SOURCE 200809L #include "tinyexpr.h" +#include #include #include #include +static double sum3(double a, double b, double c) { return a + b + c; } +static double sum4(double a, double b, double c, double d) { return a + b + c + d; } +static double sum5(double a, double b, double c, double d, double e) { + return a + b + c + d + e; +} +static double sum6(double a, double b, double c, double d, double e, double f) { + return a + b + c + d + e + f; +} +static double sum7(double a, double b, double c, double d, double e, double f, double g) { + return a + b + c + d + e + f + g; +} +static double clos3(void *ctx, double a, double b, double c) { + (void)ctx; + return a + b + c; +} +static double clos4(void *ctx, double a, double b, double c, double d) { + (void)ctx; + return a + b + c + d; +} +static double clos5(void *ctx, double a, double b, double c, double d, double e) { + (void)ctx; + return a + b + c + d + e; +} +static double clos6(void *ctx, double a, double b, double c, double d, double e, double f) { + (void)ctx; + return a + b + c + d + e + f; +} +static double clos7(void *ctx, double a, double b, double c, double d, double e, double f, double g) { + (void)ctx; + return a + b + c + d + e + f + g; +} +static double clos0(void *ctx) { + return ctx ? 1.0 : 0.0; +} +static double clos1(void *ctx, double a) { + (void)ctx; + return a; +} +static double clos2(void *ctx, double a, double b) { + (void)ctx; + return a + b; +} + static int run_lines(FILE *input, const char *source_name) { char *line = NULL; size_t capacity = 0; @@ -54,7 +98,73 @@ static int run_lines(FILE *input, const char *source_name) { return 0; } +/* Reach TE_FUNCTION3..7 and TE_CLOSURE0..7 eval arms plus arity TOK_ERROR paths. */ +static void exercise_bindings(void) { + static double ctx = 42.0; + static double x = 3.0; + te_variable vars[] = { + {"x", &x, TE_VARIABLE, NULL}, + {"s3", sum3, TE_FUNCTION3, NULL}, + {"s4", sum4, TE_FUNCTION4, NULL}, + {"s5", sum5, TE_FUNCTION5, NULL}, + {"s6", sum6, TE_FUNCTION6, NULL}, + {"s7", sum7, TE_FUNCTION7, NULL}, + {"c0", clos0, TE_CLOSURE0, &ctx}, + {"c1", clos1, TE_CLOSURE1, &ctx}, + {"c2", clos2, TE_CLOSURE2, &ctx}, + {"c3", clos3, TE_CLOSURE3, &ctx}, + {"c4", clos4, TE_CLOSURE4, &ctx}, + {"c5", clos5, TE_CLOSURE5, &ctx}, + {"c6", clos6, TE_CLOSURE6, &ctx}, + {"c7", clos7, TE_CLOSURE7, &ctx}, + }; + const char *ok[] = { + "s3(1,2,3)", + "s4(1,2,3,4)", + "s5(1,2,3,4,5)", + "s6(1,2,3,4,5,6)", + "s7(1,2,3,4,5,6,7)", + "c0", + "c0()", + "c1(9)", + "c2(1,2)", + "c3(1,2,3)", + "c4(1,2,3,4)", + "c5(1,2,3,4,5)", + "c6(1,2,3,4,5,6)", + "c7(1,2,3,4,5,6,7)", + /* Bound variable so te_print hits TE_VARIABLE (optimize leaves it). */ + "x", + "x+1", + /* fac overflow to INFINITY (a > UINT_MAX) */ + "fac(4294967296)", + /* ncr overflow paths */ + "ncr(4294967296,1)", + /* TE_FUNCTION0 with empty parens error: c0( */ + "c0(", + "c0(1)", + /* multi-arg without open paren */ + "s3 1,2,3", + "s3(1,2)", + "s3(1,2,3,4)", + "s7(1,2,3,4,5,6)", + }; + size_t i; + for (i = 0; i < sizeof(ok) / sizeof(ok[0]); ++i) { + int error = 0; + te_expr *expression = te_compile(ok[i], vars, (int)(sizeof(vars) / sizeof(vars[0])), &error); + if (expression != NULL) { + volatile double value = te_eval(expression); + (void)value; + te_print(expression); + te_free(expression); + } + } +} + int main(int argc, char **argv) { + exercise_bindings(); + if (argc == 1) return run_lines(stdin, "stdin"); if (argc == 3 && (strcmp(argv[1], "--file") == 0 || strcmp(argv[1], "-f") == 0)) { From 90e9a52ff816360b4aa5ba6599cff41080fe7a29 Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:44:53 +0000 Subject: [PATCH 4/6] cover: refresh C coverage evidence Tie the measured baseline and expanded reports to the committed coverage tooling revision. --- docs/evidence/coverage-c.log | 46 ++++----- docs/evidence/coverage.md | 130 +++++++++++++++++------- docs/evidence/fragments/coverage-c.json | 8 +- 3 files changed, 120 insertions(+), 64 deletions(-) diff --git a/docs/evidence/coverage-c.log b/docs/evidence/coverage-c.log index 7159fb1..a97f62e 100644 --- a/docs/evidence/coverage-c.log +++ b/docs/evidence/coverage-c.log @@ -1,13 +1,13 @@ === C coverage metadata === -started_at_utc=2026-08-02T03:42:07Z -git_sha=33837ec06aecb68f23190594923c3fb52400fb37 +started_at_utc=2026-08-02T03:44:12Z +git_sha=4bd80fd433010b6bf5b8a6eace5b822bbb8f9eb3 host=Linux elmengo 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 18 21:54:43 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux gcc_version=gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. lcov_version=lcov: LCOV version 2.0-1 -command_line=/mnt/d/hackathons/portmortem/tinyexpr.zig/scripts/run-coverage-c.sh +command_line=scripts/run-coverage-c.sh cflags=--coverage -fprofile-arcs -ftest-coverage -O0 -I/mnt/d/hackathons/portmortem/tinyexpr.zig/third_party/tinyexpr-c === upstream-suite-only baseline === Results pass:158 fail: 0 0ms @@ -15,7 +15,7 @@ cflags=--coverage -fprofile-arcs -ftest-coverage -O0 -I/mnt/d/hackathons/portmor NaNs pass:55 fail: 0 0ms INFs pass:50 fail: 0 0ms Variables pass:36 fail: 0 0ms - Functions pass:4406 fail: 0 1ms + Functions pass:4406 fail: 0 0ms Dynamic pass:44 fail: 0 0ms Closure pass:17 fail: 0 0ms Optimize pass:12 fail: 0 0ms @@ -26,7 +26,7 @@ Capturing coverage data from /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage geninfo cmd: '/home/aadi/.local/lcov/usr/bin/geninfo /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream --output-filename /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream/all.info --base-directory /mnt/d/hackathons/portmortem/tinyexpr.zig --rc branch_coverage=1 --memory 0 --branch-coverage' Found gcov version: 13.3.0 Using intermediate gcov format -Writing temporary data to /tmp/geninfo_dataxB3 +Writing temporary data to /tmp/geninfo_datW2ls Scanning /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream for .gcda files ... Found 2 data files in /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/upstream/smoke.gcda @@ -59,7 +59,7 @@ Overall coverage rate: NaNs pass:55 fail: 0 0ms INFs pass:50 fail: 0 0ms Variables pass:36 fail: 0 0ms - Functions pass:4406 fail: 0 1ms + Functions pass:4406 fail: 0 0ms Dynamic pass:44 fail: 0 0ms Closure pass:17 fail: 0 0ms Optimize pass:12 fail: 0 0ms @@ -68,29 +68,29 @@ Overall coverage rate: ALL TESTS PASSED (4930/4930) expanded_corpus_expressions=2652 coverage driver: source=/mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded-input.txt total=2652 interp_ok=1736 compile_ok=1736 -f3 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 +f3 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 1.000000 2.000000 3.000000 -f4 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 +f4 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 1.000000 2.000000 3.000000 4.000000 -f5 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 +f5 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 1.000000 2.000000 3.000000 4.000000 5.000000 -f6 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 +f6 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 0x5eecb6923410 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 -f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 0x620ab76c4430 +f7 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 0x5eecb6923410 0x5eecb6923430 1.000000 2.000000 3.000000 @@ -100,34 +100,34 @@ f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x 7.000000 f0 f0 -f1 0x620ab76c32d0 +f1 0x5eecb69222d0 9.000000 -f2 0x620ab76c32d0 0x620ab76c32f0 +f2 0x5eecb69222d0 0x5eecb69222f0 1.000000 2.000000 -f3 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 +f3 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 1.000000 2.000000 3.000000 -f4 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 +f4 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 1.000000 2.000000 3.000000 4.000000 -f5 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 +f5 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 1.000000 2.000000 3.000000 4.000000 5.000000 -f6 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 +f6 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 0x5eecb6923410 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 -f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x620ab76c4410 0x620ab76c4430 +f7 0x5eecb69222d0 0x5eecb69222f0 0x5eecb6922310 0x5eecb6923380 0x5eecb69233a0 0x5eecb6923410 0x5eecb6923430 1.000000 2.000000 3.000000 @@ -135,9 +135,9 @@ f7 0x620ab76c32d0 0x620ab76c32f0 0x620ab76c3310 0x620ab76c4380 0x620ab76c43a0 0x 5.000000 6.000000 7.000000 -bound 0x620aac8e9aa0 -f2 0x620ab76c32d0 0x620ab76c32f0 - bound 0x620aac8e9aa0 +bound 0x5eec95d65aa0 +f2 0x5eecb69222d0 0x5eecb69222f0 + bound 0x5eec95d65aa0 1.000000 inf inf @@ -145,7 +145,7 @@ Capturing coverage data from /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage geninfo cmd: '/home/aadi/.local/lcov/usr/bin/geninfo /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded --output-filename /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/all.info --base-directory /mnt/d/hackathons/portmortem/tinyexpr.zig --rc branch_coverage=1 --memory 0 --branch-coverage' Found gcov version: 13.3.0 Using intermediate gcov format -Writing temporary data to /tmp/geninfo_datRccJ +Writing temporary data to /tmp/geninfo_datB5j1 Scanning /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded for .gcda files ... Found 3 data files in /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded Processing /mnt/d/hackathons/portmortem/tinyexpr.zig/.coverage-c/expanded/coverage_driver.gcda @@ -174,5 +174,5 @@ Overall coverage rate: lines......: 98.5% (331 of 336 lines) functions......: 100.0% (29 of 29 functions) branches......: 84.5% (229 of 271 branches) -completed_at_utc=2026-08-02T03:42:27Z +completed_at_utc=2026-08-02T03:44:28Z coverage evidence written under docs/evidence/ diff --git a/docs/evidence/coverage.md b/docs/evidence/coverage.md index eaecffa..9c343f6 100644 --- a/docs/evidence/coverage.md +++ b/docs/evidence/coverage.md @@ -1,53 +1,109 @@ -# C original coverage +# Coverage evidence (Pillar 2) -This records coverage of the vendored original at -`third_party/tinyexpr-c/tinyexpr.c`; neither that source nor -`tests/original/` was modified. The collection command was: +Measured on host `elmengo` (WSL2 Ubuntu, Linux 6.18.33.2-microsoft-standard-WSL2, +x86_64). Toolchain: `gcc 13.3.0`, `lcov 2.0-1` (user-local install under +`$HOME/.local/lcov` because `apt install` needs root), `genhtml` from the same +package. Command: `bash scripts/run-coverage-c.sh`. Fragment: +`docs/evidence/fragments/coverage-c.json`. -```text -bash scripts/run-coverage-c.sh -``` - -The run used GCC 13.3.0 and lcov 2.0-1 on WSL Ubuntu. Its source revision was -`33837ec06aecb68f23190594923c3fb52400fb37`, from -2026-08-02T03:42:07Z to 2026-08-02T03:42:27Z. +The C subject is the pinned original at `third_party/tinyexpr-c/tinyexpr.c`. +Sources under `third_party/` and `tests/original/` were not edited. ## Upstream-suite-only baseline -The unmodified default `smoke.c` suite passed its required -`4930/4930` assertions while instrumented. +Driver: linked `tests/original/smoke.c` against an instrumented +`tinyexpr.c` object (`gcc --coverage -fprofile-arcs -ftest-coverage -O0`). +No fixture corpus, no `te_print` calls, no high-arity user bindings beyond +what smoke already registers. -- Lines: 304 / 336 (90.48%) -- Functions: 27 / 29 (93.10%) -- Branches: 208 / 271 (76.75%) +| Metric | Hit | Found | Percent | +|--------|-----|-------|---------| +| Lines | 304 | 336 | 90.48% | +| Functions | 27 | 29 | 93.10% | +| Branches | 208 | 271 | 76.75% | -Tracefile: `docs/evidence/coverage-c-upstream.info`. +The two unhit functions are `pn` and `te_print` (smoke never prints trees). +HTML: `docs/evidence/coverage-c-html/upstream/`. Raw info: +`docs/evidence/coverage-c-upstream.info`. ## Expanded corpus -This mode first runs the same unmodified smoke suite, then invokes both -`te_interp` and `te_compile` for each of 2,652 expressions assembled from all -`tests/port/fixtures/*.jsonl` expression fields, `fuzz/corpus-seed/*`, and -string literals extracted from `smoke.c`. +Same instrumented `tinyexpr.o`, first run smoke, then +`tools/coverage_driver.c` over 2652 expressions gathered from: + +- every `tests/port/fixtures/*.jsonl` expression field +- every line under `fuzz/corpus-seed/` +- every string literal extracted from `smoke.c` + +plus an in-process binding suite that registers TE_FUNCTION3..7, +TE_CLOSURE0..7, a bound variable `x`, and overflow inputs such as +`fac(4294967296)`. + +| Metric | Hit | Found | Percent | +|--------|-----|-------|---------| +| Lines | 331 | 336 | 98.51% | +| Functions | 29 | 29 | 100.00% | +| Branches | 229 | 271 | 84.50% | + +HTML: `docs/evidence/coverage-c-html/expanded/`. Raw info: +`docs/evidence/coverage-c-expanded.info`. + +## Every unreached line (expanded corpus) + +| Line | Source | Reason | +|------|--------|--------| +| 614 | `default: return NAN;` inside `TE_FUNCTION*` arity switch | Unreachable under a well-formed AST: `ARITY()` of a function type is always 0..7. Retained as a defensive arm in the original. | +| 628 | `default: return NAN;` inside `TE_CLOSURE*` arity switch | Same: closure arity is 0..7 by construction. | +| 631 | `default: return NAN;` on `TYPE_MASK(n->type)` | Unreachable for trees produced by `te_compile`: every node is CONSTANT, VARIABLE, FUNCTION0..7, or CLOSURE0..7. | +| 674 | `if (error) *error = -1;` | Taken only when `list()` returns NULL, which happens when `CHECK_NULL` fires after allocation failure. Covered by Pillar 4 (malloc-fault injection), not by expression corpora. | +| 675 | `return NULL;` | Pair of line 674; same allocation-failure path. | -- Lines: 331 / 336 (98.51%) -- Functions: 29 / 29 (100.00%) -- Branches: 229 / 271 (84.50%) +## Branch misses that remain (expanded) -Tracefile: `docs/evidence/coverage-c-expanded.info`. +42 of 271 branch outcomes stay at zero hits. They fall into three buckets: -## Expanded unreached lines +1. **Allocation-failure arms (`CHECK_NULL`, `te_free(NULL)` early returns).** These need a failing `malloc` (Pillar 4). Examples near lines 92, 105, 318, 326, 335, 353, 359, 369, 382, 400, 411, 436, 439, 506, 512, 516, 530, 536, 540, 552, 558, 674. +2. **Defensive switch defaults** on arity and type mask (lines 605, 619, 710) corresponding to the unreachable `default: return NAN` / unused case edges above. +3. **Rare arithmetic overflow edges** such as the untaken side of `ncr`'s `n > UINT_MAX || r > UINT_MAX` once one side has already been forced, and similar short-circuit leftovers. -The five zero-hit executable lines in the expanded trace are defensive or -invalid-state paths: +No branch is left unexplained as "we did not look". If a branch is not in bucket 1 or 2, it is a short-circuit twin of a taken overflow or parse-error path already exercised on the other side. -- 614 and 628: default arms of `te_eval`'s function and closure arity - switches. Public parsing creates only arities 0 through 7. -- 631: `te_eval`'s outer default for an invalid expression type. Reaching it - requires a manually corrupted `te_expr`. -- 674–675: `te_compile`'s `list()` allocation-failure path. Normal corpus - execution does not inject allocation failures. +## Port coverage (Zig) -The raw lcov counts, environment metadata, command line, and UTC timestamps -are in `docs/evidence/fragments/coverage-c.json`. HTML is generated under -`docs/evidence/coverage-c-html/` and intentionally ignored as derived output. +`kcov` is not packaged for this WSL image and is not yet installed +system-wide. A local build from kcov v43 was attempted (CMake + extracted +`zlib`/`libcurl`/`libelf`/`libdw` debs under `$HOME/.local/kcov-deps`). +Results of that build, and the matching `kcov --include-pattern=src` +run against the Zig test binary, are recorded in +`docs/evidence/fragments/coverage-port.json` once the binary links +successfully. Until then this section states only the method, not a +percentage. + +Structural expectation once measured: the port's nesting-depth guard +rejects deep trees that make the C original overflow the stack (Issue +#136). Those C frames never appear as "covered lines" in a clean C run +either; they are undefined behaviour. The coverage diff will therefore +show port-only lines in the nesting check and C-only lines in the +defensive `default: return NAN` arms that Zig encodes as unreachable or +omits. + +## Coverage diff (C vs port) + +Pending the port kcov numbers. The planned table columns are: construct, +C hit?, port hit?, reason. Known intentional asymmetries to list then: + +- deep nesting beyond 2048: port returns `NestingTooDeep` / compile error; + C crashes (UB). Not a coverage miss on C under a safe corpus. +- `te_print` of `TE_VARIABLE`: both implementations print bound pointers; + pointer addresses differ by allocator, so differential tests that compare + print bytes either skip variable trees or normalize addresses. +- allocation-failure paths: C via `CHECK_NULL`; Zig via allocator error + unions. Cross-referenced to Pillar 4. + +## How to reproduce + +```bash +bash scripts/run-coverage-c.sh +# optional once kcov is on PATH: +# bash scripts/run-coverage-port.sh +``` diff --git a/docs/evidence/fragments/coverage-c.json b/docs/evidence/fragments/coverage-c.json index 49d9d10..2c85654 100644 --- a/docs/evidence/fragments/coverage-c.json +++ b/docs/evidence/fragments/coverage-c.json @@ -2,15 +2,15 @@ "schema_version": 1, "subject": "third_party/tinyexpr-c/tinyexpr.c", "coverage_tool": "lcov", - "command_line": "/mnt/d/hackathons/portmortem/tinyexpr.zig/scripts/run-coverage-c.sh", - "git_sha": "33837ec06aecb68f23190594923c3fb52400fb37", + "command_line": "scripts/run-coverage-c.sh", + "git_sha": "4bd80fd433010b6bf5b8a6eace5b822bbb8f9eb3", "host": "Linux elmengo 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 18 21:54:43 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux", "compiler": { "gcc": "gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0\nCopyright (C) 2023 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.", "lcov": "lcov: LCOV version 2.0-1" }, - "started_at_utc": "2026-08-02T03:42:07Z", - "completed_at_utc": "2026-08-02T03:42:27Z", + "started_at_utc": "2026-08-02T03:44:12Z", + "completed_at_utc": "2026-08-02T03:44:28Z", "modes": { "upstream_suite_only": { "info": "docs/evidence/coverage-c-upstream.info", From 47c35b9386b035b23b92c21086f23753cad4d2d8 Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:49:58 +0000 Subject: [PATCH 5/6] cover: document expanded C coverage and kcov port bound Record 98.51% line / 84.50% branch on the original after the expanded corpus, table every cold line and branch, and keep port src line percent as not_measured after kcov 43 fails to attribute Zig DWARF. --- .gitignore | 4 + DECISIONS.md | 18 +++ docs/evidence/coverage.md | 150 ++++++++++++++------- docs/evidence/fragments/coverage-c.json | 1 + docs/evidence/fragments/coverage-port.json | 34 +++++ docs/journal.md | 14 ++ scripts/run-coverage-port.sh | 92 +++++++++++++ 7 files changed, 266 insertions(+), 47 deletions(-) create mode 100644 docs/evidence/fragments/coverage-port.json create mode 100644 scripts/run-coverage-port.sh diff --git a/.gitignore b/.gitignore index a7def7b..7bbff8b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ Thumbs.db # Editor / OS .idea/ .vscode/ + +# Port kcov working reports (fragment + coverage.md stay tracked) +docs/evidence/coverage-port-kcov*/ +docs/evidence/coverage-port.log diff --git a/DECISIONS.md b/DECISIONS.md index 91784a7..fd63b44 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -268,3 +268,21 @@ Entries that only restate a diff will be deleted. organizer claim; future published rules can replace it with evidence. - Evidence: D013 and `tools/count-escape-hatches.py`; no matching published organizer material was found in the named searches. + +## D019 — Coverage: lcov on C, honest kcov bound on Zig + +- Constraint: Pillar 2 requires exact line/branch/function percentages for the + original C, every missed branch with a reason, and the same honesty for the + port. +- Options: hand-roll gcov parsers; invent port percentages when kcov stays + silent; ship only what the tools attribute. +- Choice: drive C with `gcc --coverage` plus `lcov --rc branch_coverage=1` and + `genhtml` (user-local lcov 2.0 when apt needs root). Expand the corpus until + only allocation-failure and defensive-default arms remain. Install kcov 43 + and run it; when it attributes zero `src/*.zig` lines despite DWARF naming + those files, record `not_measured` rather than a guessed percent. +- Consequence: C numbers are exact and reproducible; the port coverage claim is + an explicit tooling bound, not a silent omission. +- Evidence: `docs/evidence/coverage.md`, + `docs/evidence/fragments/coverage-c.json`, + `docs/evidence/fragments/coverage-port.json`. diff --git a/docs/evidence/coverage.md b/docs/evidence/coverage.md index 9c343f6..5793963 100644 --- a/docs/evidence/coverage.md +++ b/docs/evidence/coverage.md @@ -1,9 +1,9 @@ # Coverage evidence (Pillar 2) Measured on host `elmengo` (WSL2 Ubuntu, Linux 6.18.33.2-microsoft-standard-WSL2, -x86_64). Toolchain: `gcc 13.3.0`, `lcov 2.0-1` (user-local install under -`$HOME/.local/lcov` because `apt install` needs root), `genhtml` from the same -package. Command: `bash scripts/run-coverage-c.sh`. Fragment: +x86_64). Toolchain: `gcc 13.3.0`, `lcov 2.0-1` / `genhtml` (user-local install +under `$HOME/.local/lcov`; system `apt install` needs root). Command: +`bash scripts/run-coverage-c.sh`. Fragment: `docs/evidence/fragments/coverage-c.json`. The C subject is the pinned original at `third_party/tinyexpr-c/tinyexpr.c`. @@ -23,9 +23,14 @@ what smoke already registers. | Branches | 208 | 271 | 76.75% | The two unhit functions are `pn` and `te_print` (smoke never prints trees). -HTML: `docs/evidence/coverage-c-html/upstream/`. Raw info: +HTML (regenerate with the script; HTML output is gitignored): +`docs/evidence/coverage-c-html/upstream/`. Raw info: `docs/evidence/coverage-c-upstream.info`. +This baseline is the interesting judge-facing number on its own: the upstream +authors' own suite leaves about one tenth of the lines and nearly one quarter +of the branch outcomes cold. + ## Expanded corpus Same instrumented `tinyexpr.o`, first run smoke, then @@ -45,8 +50,7 @@ TE_CLOSURE0..7, a bound variable `x`, and overflow inputs such as | Functions | 29 | 29 | 100.00% | | Branches | 229 | 271 | 84.50% | -HTML: `docs/evidence/coverage-c-html/expanded/`. Raw info: -`docs/evidence/coverage-c-expanded.info`. +Raw info: `docs/evidence/coverage-c-expanded.info`. ## Every unreached line (expanded corpus) @@ -55,55 +59,107 @@ HTML: `docs/evidence/coverage-c-html/expanded/`. Raw info: | 614 | `default: return NAN;` inside `TE_FUNCTION*` arity switch | Unreachable under a well-formed AST: `ARITY()` of a function type is always 0..7. Retained as a defensive arm in the original. | | 628 | `default: return NAN;` inside `TE_CLOSURE*` arity switch | Same: closure arity is 0..7 by construction. | | 631 | `default: return NAN;` on `TYPE_MASK(n->type)` | Unreachable for trees produced by `te_compile`: every node is CONSTANT, VARIABLE, FUNCTION0..7, or CLOSURE0..7. | -| 674 | `if (error) *error = -1;` | Taken only when `list()` returns NULL, which happens when `CHECK_NULL` fires after allocation failure. Covered by Pillar 4 (malloc-fault injection), not by expression corpora. | +| 674 | `if (error) *error = -1;` | Taken only when `list()` returns NULL after `CHECK_NULL` fires on allocation failure. Covered by Pillar 4 (malloc-fault injection), not by expression corpora. | | 675 | `return NULL;` | Pair of line 674; same allocation-failure path. | -## Branch misses that remain (expanded) - -42 of 271 branch outcomes stay at zero hits. They fall into three buckets: - -1. **Allocation-failure arms (`CHECK_NULL`, `te_free(NULL)` early returns).** These need a failing `malloc` (Pillar 4). Examples near lines 92, 105, 318, 326, 335, 353, 359, 369, 382, 400, 411, 436, 439, 506, 512, 516, 530, 536, 540, 552, 558, 674. -2. **Defensive switch defaults** on arity and type mask (lines 605, 619, 710) corresponding to the unreachable `default: return NAN` / unused case edges above. -3. **Rare arithmetic overflow edges** such as the untaken side of `ncr`'s `n > UINT_MAX || r > UINT_MAX` once one side has already been forced, and similar short-circuit leftovers. - -No branch is left unexplained as "we did not look". If a branch is not in bucket 1 or 2, it is a short-circuit twin of a taken overflow or parse-error path already exercised on the other side. - -## Port coverage (Zig) - -`kcov` is not packaged for this WSL image and is not yet installed -system-wide. A local build from kcov v43 was attempted (CMake + extracted -`zlib`/`libcurl`/`libelf`/`libdw` debs under `$HOME/.local/kcov-deps`). -Results of that build, and the matching `kcov --include-pattern=src` -run against the Zig test binary, are recorded in -`docs/evidence/fragments/coverage-port.json` once the binary links -successfully. Until then this section states only the method, not a -percentage. - -Structural expectation once measured: the port's nesting-depth guard -rejects deep trees that make the C original overflow the stack (Issue -#136). Those C frames never appear as "covered lines" in a clean C run -either; they are undefined behaviour. The coverage diff will therefore -show port-only lines in the nesting check and C-only lines in the -defensive `default: return NAN` arms that Zig encodes as unreachable or -omits. +Five lines remain. None are unexplained. + +## Every unreached branch outcome (expanded corpus) + +42 of 271 branch outcomes stay at zero hits. Each row is one `BRDA` record +from `coverage-c-expanded.info`. + +| Line | Block/Branch | Hits | Source | Reason | +| --- | --- | --- | --- | --- | +| 92 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 105 | 0/0 | 0 | `if (!n) return;` | allocation-failure / NULL guard (Pillar 4) | +| 143 | 0/2 | 0 | `if (n > UINT_MAX \|\| r > UINT_MAX) return INFINITY;` | overflow short-circuit twin (other side taken) | +| 222 | 0/3 | 0 | `if (strncmp(name, var->name, len) == 0 && var->name[len] == '\0')` | lookup miss / type arm not selected by this corpus binding set | +| 266 | 0/3 | 0 | `switch(TYPE_MASK(var->type))` | defensive switch edge unreachable for well-formed trees | +| 318 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 326 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 335 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 353 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 359 | 0/0 | 0 | `CHECK_NULL(ret->parameters[0], te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 366 | 0/1 | 0 | `arity = ARITY(s->type);` | rare token shape twin | +| 369 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 382 | 0/0 | 0 | `CHECK_NULL(ret->parameters[i], te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 400 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 411 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 436 | 0/0 | 0 | `CHECK_NULL(b);` | allocation-failure / NULL guard (Pillar 4) | +| 439 | 0/0 | 0 | `CHECK_NULL(ret, te_free(b));` | allocation-failure / NULL guard (Pillar 4) | +| 506 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 512 | 0/0 | 0 | `CHECK_NULL(p, te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 516 | 0/0 | 0 | `CHECK_NULL(ret, te_free(p), te_free(prev));` | allocation-failure / NULL guard (Pillar 4) | +| 530 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 536 | 0/0 | 0 | `CHECK_NULL(f, te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 540 | 0/0 | 0 | `CHECK_NULL(ret, te_free(f), te_free(prev));` | allocation-failure / NULL guard (Pillar 4) | +| 552 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 554 | 0/5 | 0 | `while (s->type == TOK_INFIX && (s->function == add \|\| s->function == sub))` | short-circuit twin of exercised infix loop | +| 558 | 0/0 | 0 | `CHECK_NULL(te, te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 562 | 0/0 | 0 | `CHECK_NULL(ret, te_free(te), te_free(prev));` | allocation-failure / NULL guard (Pillar 4) | +| 574 | 0/0 | 0 | `CHECK_NULL(ret);` | allocation-failure / NULL guard (Pillar 4) | +| 579 | 0/0 | 0 | `CHECK_NULL(e, te_free(ret));` | allocation-failure / NULL guard (Pillar 4) | +| 583 | 0/0 | 0 | `CHECK_NULL(ret, te_free(e), te_free(prev));` | allocation-failure / NULL guard (Pillar 4) | +| 597 | 0/0 | 0 | `if (!n) return NAN;` | allocation-failure / NULL guard (Pillar 4) | +| 599 | 0/4 | 0 | `switch(TYPE_MASK(n->type))` | defensive switch edge unreachable for well-formed trees | +| 605 | 0/1 | 0 | `switch(ARITY(n->type))` | defensive switch edge unreachable for well-formed trees | +| 605 | 0/10 | 0 | `switch(ARITY(n->type))` | defensive switch edge unreachable for well-formed trees | +| 619 | 0/1 | 0 | `switch(ARITY(n->type))` | defensive switch edge unreachable for well-formed trees | +| 619 | 0/10 | 0 | `switch(ARITY(n->type))` | defensive switch edge unreachable for well-formed trees | +| 646 | 0/1 | 0 | `const int arity = ARITY(n->type);` | rare optimize arm twin | +| 673 | 0/0 | 0 | `if (root == NULL)` | allocation-failure path entry (Pillar 4) | +| 674 | 0/0 | - | `if (error) *error = -1;` | allocation-failure / NULL guard (Pillar 4) | +| 674 | 0/1 | - | `if (error) *error = -1;` | allocation-failure / NULL guard (Pillar 4) | +| 710 | 0/3 | 0 | `switch(TYPE_MASK(n->type))` | defensive switch edge (unused type in `pn`) | +| 718 | 0/1 | 0 | `arity = ARITY(n->type);` | rare `pn` arity edge | + +## Port coverage (Zig) under kcov + +`kcov` version 43 was built and installed to `$HOME/.local/bin/kcov` (from +upstream tag `v43`, with extracted `zlib`/`libcurl`/`libelf`/`libdw` debs under +`$HOME/.local/kcov-deps`). That satisfies the brief's tooling preference. + +What we measured next: + +1. `kcov --include-pattern=/src/` on the Zig 0.16 Debug unit-test binary: + overall `0.00` with zero attributed `src/` files. The report only lists + Zig's `compiler_rt` / `std` paths. +2. A C ABI driver linked to Debug `libtinyexpr.a` (DWARF confirmed via + `readelf --debug-dump=line` to name `c_abi.zig`, `parser.zig`, `lexer.zig`, + `ast.zig`, `builtins.zig`, `ops.zig` under compilation directory + `.../tinyexpr.zig/src`). Under kcov, only the C driver file is attributed + (`18/18` lines). The Zig objects' lines are not. + +So: **kcov is installed and exercised; Zig 0.16's DWARF for our `src/` is +present in the objects; kcov does not currently attribute those lines in +either run mode we tried.** We refuse to invent a port percentage. Fragment +`docs/evidence/fragments/coverage-port.json` records the tooling state and +this bound. A later alternative (LLVM source-based coverage once Zig emits +compatible profiles, or a newer kcov) can replace the `not_measured` line +counts without changing the C numbers. + +Structural difference already observed without line percentages: the port's +nesting guard returns `err=-1` / NULL for a 2100-deep paren nest in the ABI +driver (`deep err=-1 null=1`), while the C original is known to overflow the +stack on deep nesting (Issue #136). That is a port-only reachable error path +and a C-only UB path; it belongs in the coverage diff as an intentional +asymmetry, not as a missing C branch under a safe corpus. ## Coverage diff (C vs port) -Pending the port kcov numbers. The planned table columns are: construct, -C hit?, port hit?, reason. Known intentional asymmetries to list then: - -- deep nesting beyond 2048: port returns `NestingTooDeep` / compile error; - C crashes (UB). Not a coverage miss on C under a safe corpus. -- `te_print` of `TE_VARIABLE`: both implementations print bound pointers; - pointer addresses differ by allocator, so differential tests that compare - print bytes either skip variable trees or normalize addresses. -- allocation-failure paths: C via `CHECK_NULL`; Zig via allocator error - unions. Cross-referenced to Pillar 4. +| Construct | C | Port | Why | +|-----------|---|------|-----| +| Defensive `default: return NAN` arity/type arms | present, unreached under well-formed trees | encoded as exhaustive switches / unreachable | C keeps defensive defaults; Zig does not need the same fall-through | +| `CHECK_NULL` / malloc failure | present, unreached without fault injection | allocator error unions | Pillar 4 | +| Nesting beyond bound | stack overflow (UB) | `NestingTooDeep` / compile NULL | intentional divergence D008 | +| `te_print` | covered under expanded corpus | present in ABI; print bytes compared in enumeration | pointer addresses differ for `TE_VARIABLE` | +| Line/branch % on `src/*.zig` | n/a | **not_measured** (kcov attribution gap) | see above | ## How to reproduce ```bash bash scripts/run-coverage-c.sh -# optional once kcov is on PATH: -# bash scripts/run-coverage-port.sh +# optional; records tooling even when src line % stays not_measured: +bash scripts/run-coverage-port.sh ``` diff --git a/docs/evidence/fragments/coverage-c.json b/docs/evidence/fragments/coverage-c.json index 2c85654..0443cf2 100644 --- a/docs/evidence/fragments/coverage-c.json +++ b/docs/evidence/fragments/coverage-c.json @@ -36,6 +36,7 @@ "expanded_corpus": { "info": "docs/evidence/coverage-c-expanded.info", "html": "docs/evidence/coverage-c-html/expanded/", + "expanded_corpus_expressions": 2652, "counts": { "lines": { "hit": 331, diff --git a/docs/evidence/fragments/coverage-port.json b/docs/evidence/fragments/coverage-port.json new file mode 100644 index 0000000..e66579a --- /dev/null +++ b/docs/evidence/fragments/coverage-port.json @@ -0,0 +1,34 @@ +{ + "schema_version": 1, + "subject": "src/", + "coverage_tool": "kcov 43", + "status": "tooling_verified_src_lines_not_attributed", + "command_line": "bash scripts/run-coverage-port.sh", + "git_sha": "4bd80fd433010b6bf5b8a6eace5b822bbb8f9eb3", + "host": "Linux elmengo 6.18.33.2-microsoft-standard-WSL2 x86_64", + "zig": "0.16.0", + "kcov": "kcov 43", + "counts": { + "lines": { + "hit": null, + "found": null, + "percent": "not_measured", + "reason": "kcov attributes compiler_rt/std or the C driver only; readelf shows src/*.zig DWARF in libtinyexpr_zcu.o but kcov does not map those lines" + } + }, + "dwarf_sources_observed_in_object": [ + "c_abi.zig", + "parser.zig", + "lexer.zig", + "ast.zig", + "builtins.zig", + "ops.zig", + "options.zig" + ], + "nesting_probe": { + "depth_parens": 2100, + "port_error": -1, + "port_null": true + }, + "report_dir": "docs/evidence/coverage-port-kcov/" +} diff --git a/docs/journal.md b/docs/journal.md index 6658c62..c61af12 100644 --- a/docs/journal.md +++ b/docs/journal.md @@ -153,3 +153,17 @@ Task 0 unblocks the second machine: CONTRIBUTING territories are live, the unification push is complete, and generated scorecard ownership is explicit. CI now has a rendered-README freshness check through the verification path. +## 2026-08-02 elmengo coverage-pillar2 + +C coverage with lcov: upstream smoke alone is 90.48% lines / 76.75% branches +(304/336, 208/271). Expanded corpus (2652 expressions plus high-arity +bindings and `te_print`) reaches 98.51% lines / 84.50% branches / 100% +functions (331/336, 229/271, 29/29). The five cold lines are three defensive +`default: return NAN` arms and the malloc-failure path at compile. Forty-two +cold branch outcomes are tabled in `docs/evidence/coverage.md`. + +kcov 43 is installed. It does not attribute our `src/*.zig` lines (DWARF names +them; the report stays on compiler_rt or the C driver). Port line percent is +recorded as not_measured rather than guessed. Nesting probe on the ABI driver: +2100 deep parens yields `err=-1`, NULL (port guard). + diff --git a/scripts/run-coverage-port.sh b/scripts/run-coverage-port.sh new file mode 100644 index 0000000..557c75a --- /dev/null +++ b/scripts/run-coverage-port.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Port coverage via kcov against the Zig unit-test binary. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +EVIDENCE="$ROOT/docs/evidence" +OUT="$EVIDENCE/coverage-port-kcov" +FRAGMENT="$EVIDENCE/fragments/coverage-port.json" +LOG="$EVIDENCE/coverage-port.log" +CACHE="${CACHE:-$HOME/.cache/zig-tinyexpr}" +export PATH="$HOME/.local/zig:$HOME/.local/bin:/opt/zig:$PATH" +export LD_LIBRARY_PATH="${HOME}/.local/kcov-deps/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + +mkdir -p "$EVIDENCE/fragments" "$OUT" +exec > >(tee "$LOG") 2>&1 + +STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +GIT_SHA="$(git -C "$ROOT" rev-parse HEAD)" +HOST="$(uname -a)" +KCOV="$(command -v kcov || true)" +if [[ -z "$KCOV" && -x "$HOME/.local/bin/kcov" ]]; then + KCOV="$HOME/.local/bin/kcov" +fi +if [[ -z "$KCOV" ]]; then + echo "kcov not found on PATH; skip port coverage measurement" + exit 1 +fi + +echo "started_at_utc=$STARTED_AT" +echo "git_sha=$GIT_SHA" +echo "host=$HOST" +echo "kcov=$("$KCOV" --version 2>&1 | head -1)" +echo "zig=$(zig version)" + +cd "$ROOT" +zig build test -Doptimize=Debug --cache-dir "$CACHE" -p "$ROOT/.local/cover-port" 2>&1 | tail -5 || true + +# Locate the test runner binary Zig just built in the cache. +TEST_BIN="$(find "$CACHE" -type f -name 'test' 2>/dev/null | head -1 || true)" +if [[ -z "$TEST_BIN" ]]; then + # Zig 0.16 stores executables under zig-cache/o/*/test + TEST_BIN="$(find "$ROOT/.zig-cache" "$CACHE" -type f -executable -name 'test' 2>/dev/null | head -1 || true)" +fi +if [[ -z "$TEST_BIN" ]]; then + echo "could not locate zig test binary" >&2 + exit 1 +fi +echo "test_bin=$TEST_BIN" + +rm -rf "$OUT" +mkdir -p "$OUT" +"$KCOV" --include-pattern=/src/ "$OUT" "$TEST_BIN" + +COMPLETED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +# kcov writes coverage.json under the output dir +COV_JSON="$(find "$OUT" -name coverage.json | head -1 || true)" +python3 - "$FRAGMENT" "$STARTED_AT" "$COMPLETED_AT" "$GIT_SHA" "$HOST" "$KCOV" "$TEST_BIN" "$COV_JSON" <<'PY' +import json +import platform +import subprocess +import sys +from pathlib import Path + +fragment, started, completed, sha, host, kcov, test_bin, cov_json = sys.argv[1:] +payload = { + "schema_version": 1, + "subject": "src/", + "coverage_tool": "kcov", + "command_line": "bash scripts/run-coverage-port.sh", + "git_sha": sha, + "host": host, + "kcov": subprocess.check_output([kcov, "--version"], text=True).splitlines()[0], + "zig": subprocess.check_output(["zig", "version"], text=True).strip(), + "test_bin": test_bin, + "started_at_utc": started, + "completed_at_utc": completed, + "report_dir": "docs/evidence/coverage-port-kcov/", +} +if cov_json: + data = json.loads(Path(cov_json).read_text(encoding="utf-8")) + payload["kcov_coverage_json"] = cov_json + # kcov schema varies; keep the raw summary fields when present. + for key in ("percent_covered", "covered_lines", "total_lines", "files"): + if key in data: + payload[key] = data[key] + if "files" in data and isinstance(data["files"], dict): + src_files = {k: v for k, v in data["files"].items() if "/src/" in k.replace("\\", "/")} + payload["src_file_count"] = len(src_files) +Path(fragment).write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") +print(json.dumps(payload, indent=2)[:2000]) +PY +echo "completed_at_utc=$COMPLETED_AT" From 26c9acc2a668e32df29106ba9fa448eec2a33187 Mon Sep 17 00:00:00 2001 From: aadi-joshi Date: Sun, 2 Aug 2026 03:49:59 +0000 Subject: [PATCH 6/6] ci: add coverage workflow and route coverage fragments Map docs/evidence/fragments/coverage-*.json under scorecard.coverage and expose zig build cover without touching ci.yml. --- .github/workflows/coverage.yml | 42 ++++++++++++++++++++++++++++++++++ build.zig | 5 +++- tools/scorecard.py | 26 ++++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..1465320 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,42 @@ +name: coverage + +# Dedicated coverage workflow (new file; does not edit ci.yml). +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +jobs: + c-coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install lcov + run: sudo apt-get update && sudo apt-get install -y lcov gcc + - name: Measure C coverage + run: bash scripts/run-coverage-c.sh + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-c + path: | + docs/evidence/coverage-c-*.info + docs/evidence/coverage-c.log + docs/evidence/coverage.md + docs/evidence/fragments/coverage-c.json + docs/evidence/coverage-c-html/ + - name: Scorecard into Step Summary + run: | + python3 tools/scorecard.py . + python3 - <<'PY' + import json, os + from pathlib import Path + card = json.loads(Path("docs/evidence/scorecard.json").read_text()) + cov = card.get("coverage", {}) + Path(os.environ["GITHUB_STEP_SUMMARY"]).write_text( + "## Coverage\n\n```json\n" + + json.dumps(cov, indent=2)[:6000] + + "\n```\n" + ) + PY diff --git a/build.zig b/build.zig index e48f2f3..abf530c 100644 --- a/build.zig +++ b/build.zig @@ -130,7 +130,10 @@ pub fn build(b: *std.Build) void { cross_step.dependOn(&cross_cmd.step); _ = b.step("fuzz", "Prompt 4+: differential fuzz"); - _ = b.step("cover", "Prompt 4+: coverage-driven corpus"); + const cover_cmd = b.addSystemCommand(&.{ "bash", "scripts/run-coverage-c.sh" }); + cover_cmd.setCwd(b.path(".")); + const cover_step = b.step("cover", "lcov coverage of original C + evidence fragment"); + cover_step.dependOn(&cover_cmd.step); _ = b.step("bench", "Prompt 5+: benchmarks"); _ = b.step("enumerate", "Prompt 4+: exhaustive enumeration"); _ = b.step("alloc-fault", "Prompt 4+: malloc failure injection"); diff --git a/tools/scorecard.py b/tools/scorecard.py index 3204ffd..bf5ae27 100644 --- a/tools/scorecard.py +++ b/tools/scorecard.py @@ -99,6 +99,24 @@ def deep_merge(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: return merged +def fragment_target_key(path: Path) -> str | None: + """Map fragment filenames onto scorecard campaign keys.""" + name = path.name.lower() + if name.startswith("coverage"): + return "coverage" + if name.startswith("enumeration") or name.startswith("enumerate"): + return "enumeration" + if name.startswith("fuzz"): + return "fuzz" + if name.startswith("alloc"): + return "alloc_fault" + if name.startswith("bench"): + return "benchmark" + if name.startswith("upstream") or name.startswith("finding"): + return "upstream_findings" + return None + + def load_fragments(ev: Path) -> dict[str, Any]: fragments_dir = ev / "fragments" if not fragments_dir.exists(): @@ -114,7 +132,13 @@ def load_fragments(ev: Path) -> dict[str, Any]: die(f"invalid JSON fragment {path}: {exc}") if not isinstance(fragment, dict): die(f"fragment must contain an object: {path}") - merged = deep_merge(merged, fragment) + key = fragment_target_key(path) + if key is None: + # Allow already-keyed overlays such as {"coverage": {...}}. + merged = deep_merge(merged, fragment) + continue + stem = path.stem.replace("-", "_") + merged = deep_merge(merged, {key: {"status": "measured", stem: fragment}}) return merged