From ea941915a72cdd4f4b8489a2094c30dcb3db0ec8 Mon Sep 17 00:00:00 2001 From: Vsevolod Strukchinsky Date: Thu, 10 Sep 2026 12:02:52 +0500 Subject: [PATCH 1/2] CI: publish coverage to Codecov, and a badge for it awesome-go asks a submission for a link to a coverage report, and there was none to give: the number existed only in a CI log. The profile the test step already writes is uploaded now, so the badge is the library's real coverage and the link is somewhere to read it. It covers di, dihttp and dislog. examples/ and benchmarks/ are separate modules and not part of the library's number, which is what the split bought. The upload runs with fail_ci_if_error: false. An outage at Codecov is not a reason to fail a build, so a missing report is a stale badge and never a red gate. The badge stays unknown until the repository is activated at codecov.io and the first upload from main lands. --- .github/workflows/ci.yml | 10 ++++++++++ CLAUDE.md | 5 +++++ README.md | 1 + 3 files changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 533ab68..1df9cb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,16 @@ jobs: go run ./observe - name: test run: go test -race -count=1 -coverprofile=coverage.out ./... + # The profile the step above just wrote, which covers di, dihttp and + # dislog -- examples/ and benchmarks/ are separate modules and not part + # of the library's number. An outage at Codecov is not a reason to fail + # a build, so the upload cannot break the gate. + - name: upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage.out + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false - name: what only the hand-written tests reach run: | go test -count=1 -run 'TestMachine|TestConcurrent|TestProperty|FuzzMachine' -coverprofile=generators.out . diff --git a/CLAUDE.md b/CLAUDE.md index 5ca81d2..a2a067e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -592,6 +592,11 @@ reverse is caught by the fuzzer in 0.06s and *not* by the 400 seeded sequences. - **README code blocks are generated.** They are embedded from `examples/` with embedmd markers. Run `gofmt -w` on an example *before* re-embedding, or CI fails on the sync check. +- **Coverage is published to Codecov** from the profile CI already writes, + which covers `di`, `dihttp` and `dislog` only -- the two separate modules + are not part of the library's number. The upload runs with + `fail_ci_if_error: false`, because an outage there is not a reason to fail a + build, so a missing report is a stale badge and never a red gate. - **`examples/` and `benchmarks/` are separate modules**, each with a `replace ../` directive, so the root module keeps zero requires and the library's "no dependency outside the standard library" claim stays true. diff --git a/README.md b/README.md index 83e1c56..2a48407 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![CI](https://github.com/floatdrop/di/actions/workflows/ci.yml/badge.svg)](https://github.com/floatdrop/di/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/floatdrop/di.svg)](https://pkg.go.dev/github.com/floatdrop/di) +[![codecov](https://codecov.io/gh/floatdrop/di/branch/main/graph/badge.svg)](https://codecov.io/gh/floatdrop/di) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) A dependency-injection container for Go 1.27+. Constructors are plain From d5c825a9598bbd0485fd5f52487014608a3234fd Mon Sep 17 00:00:00 2001 From: Vsevolod Strukchinsky Date: Thu, 10 Sep 2026 12:11:05 +0500 Subject: [PATCH 2/2] Coverage: publish the report with the site instead of to a service Codecov could not be activated -- its login returns 403 -- so the badge the previous commit added would have read unknown forever. awesome-go's own COVERAGE.md suggests tj-actions/coverage-badge-go instead, which rewrites the README from CI and commits it back to main on every push; that bypasses the pull-request process this repo otherwise follows, and adds a dependency on an action whose sibling was compromised last year. So the report goes where this repo already publishes: pages.yml writes go tool cover -html and a shields endpoint JSON into site/build after the site is built and before the artifact is uploaded, and the badge in the README points at both. The report and the badge are one deploy and cannot disagree, there is nothing to log into, and no CI job commits to main. The site's path filter gains **/*.go, because coverage moves when the library or its tests do. That is the thing to remember when the badge looks stale: it is documented in CLAUDE.md and in site/README.md, whose count of things worth knowing goes to eight. The colour thresholds were checked under bash -e, which is the shell Actions uses: an awk && color= chain aborts the step if the failing awk is not in the position bash exempts. Five coverage figures were run through it, and each picks its colour and reaches the end. --- .github/workflows/ci.yml | 10 ---------- .github/workflows/pages.yml | 31 +++++++++++++++++++++++++++++-- CLAUDE.md | 15 ++++++++++----- README.md | 2 +- site/README.md | 12 +++++++++++- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1df9cb1..533ab68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,16 +42,6 @@ jobs: go run ./observe - name: test run: go test -race -count=1 -coverprofile=coverage.out ./... - # The profile the step above just wrote, which covers di, dihttp and - # dislog -- examples/ and benchmarks/ are separate modules and not part - # of the library's number. An outage at Codecov is not a reason to fail - # a build, so the upload cannot break the gate. - - name: upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - files: coverage.out - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false - name: what only the hand-written tests reach run: | go test -count=1 -run 'TestMachine|TestConcurrent|TestProperty|FuzzMachine' -coverprofile=generators.out . diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 1ceed0f..7eed14f 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -7,13 +7,18 @@ name: Site # # Node 24 is a floor, not a preference: the build and prerender scripts are # TypeScript run straight by node, which needs its type stripping. +# +# The artifact also carries the coverage report, which is why a change to any +# Go file rebuilds the site: coverage is the library's number, so it moves +# when the library or its tests do, and narrowing these paths back to site/ +# would leave the badge reading a figure from whenever the site last changed. on: push: branches: [main] - paths: [site/**, examples/guide/**, .github/workflows/pages.yml] + paths: [site/**, examples/guide/**, "**/*.go", go.mod, .github/workflows/pages.yml] pull_request: - paths: [site/**, examples/guide/**, .github/workflows/pages.yml] + paths: [site/**, examples/guide/**, "**/*.go", go.mod, .github/workflows/pages.yml] workflow_dispatch: permissions: @@ -28,6 +33,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + check-latest: true - uses: actions/setup-node@v5 with: node-version: 24 @@ -41,6 +50,24 @@ jobs: working-directory: site env: BASE_PATH: /di + # Written into the built site rather than served from anywhere else, so + # the report and the badge it links from are one deploy and cannot + # disagree. The profile covers di, dihttp and dislog: examples/ and + # benchmarks/ are separate modules and not part of the number. + - name: coverage report and badge, into the artifact + run: | + go test -count=1 -coverprofile=coverage.out ./... + go tool cover -html=coverage.out -o site/build/coverage.html + pct=$(go tool cover -func=coverage.out | tail -1 | awk '{print $NF}') + n=${pct%\%} + color=red + awk -v n="$n" 'BEGIN { exit !(n >= 90) }' && color=brightgreen + awk -v n="$n" 'BEGIN { exit !(n >= 80 && n < 90) }' && color=green + awk -v n="$n" 'BEGIN { exit !(n >= 70 && n < 80) }' && color=yellowgreen + awk -v n="$n" 'BEGIN { exit !(n >= 60 && n < 70) }' && color=yellow + printf '{"schemaVersion":1,"label":"coverage","message":"%s","color":"%s"}\n' \ + "$pct" "$color" > site/build/coverage.json + echo "coverage $pct ($color)" - uses: actions/upload-pages-artifact@v4 with: path: site/build diff --git a/CLAUDE.md b/CLAUDE.md index a2a067e..d27b578 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -592,11 +592,16 @@ reverse is caught by the fuzzer in 0.06s and *not* by the 400 seeded sequences. - **README code blocks are generated.** They are embedded from `examples/` with embedmd markers. Run `gofmt -w` on an example *before* re-embedding, or CI fails on the sync check. -- **Coverage is published to Codecov** from the profile CI already writes, - which covers `di`, `dihttp` and `dislog` only -- the two separate modules - are not part of the library's number. The upload runs with - `fail_ci_if_error: false`, because an outage there is not a reason to fail a - build, so a missing report is a stale badge and never a red gate. +- **Coverage is published with the site, not to a service.** `pages.yml` + writes `go tool cover -html` and a shields endpoint JSON into `site/build` + before uploading the artifact, so the report at + `floatdrop.github.io/di/coverage.html` and the badge that links to it are + one deploy and cannot disagree. It covers `di`, `dihttp` and `dislog`: the + two separate modules are not part of the library's number. **That is why the + site workflow's path filter includes `**/*.go`** -- coverage moves when the + library or its tests do, and narrowing the paths back to `site/` would leave + the badge reading a figure from whenever the site last changed, with nothing + failing to say so. - **`examples/` and `benchmarks/` are separate modules**, each with a `replace ../` directive, so the root module keeps zero requires and the library's "no dependency outside the standard library" claim stays true. diff --git a/README.md b/README.md index 2a48407..11c505a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/floatdrop/di/actions/workflows/ci.yml/badge.svg)](https://github.com/floatdrop/di/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/floatdrop/di.svg)](https://pkg.go.dev/github.com/floatdrop/di) -[![codecov](https://codecov.io/gh/floatdrop/di/branch/main/graph/badge.svg)](https://codecov.io/gh/floatdrop/di) +[![coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Ffloatdrop.github.io%2Fdi%2Fcoverage.json)](https://floatdrop.github.io/di/coverage.html) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) A dependency-injection container for Go 1.27+. Constructors are plain diff --git a/site/README.md b/site/README.md index 3a302bf..e36d71f 100644 --- a/site/README.md +++ b/site/README.md @@ -83,7 +83,7 @@ Add its id to `StepId`, then add the step to `steps` in **every** locale; `tsc` will not let you forget one. If it shows a new file, add the id to `FigureId`, the import to `src/code.ts`, and the figure to `buildFigures`. -## Seven things worth knowing +## Eight things worth knowing `src/inline-script.ts` is inlined by calling `Function.prototype.toString` on it, so it is cut out of its module and must close over nothing: everything it @@ -132,3 +132,13 @@ Gravity UI's own `styles/fonts.css` pulls Inter from Google Fonts. Nothing third-party is on the path to rendering the page, so `src/styles/main.css` overrides the font stack instead; there is a comment there saying how to get Inter back. + +`site/build` is not only the site. `pages.yml` writes `coverage.html` and +`coverage.json` into it after `npm run build` and before the artifact is +uploaded, so the coverage report is served by the same deploy as the pages +that link to it. Two consequences: a local `npm run build` gives a tree +without them, and the report is the one thing on this site no local build +reproduces; and the workflow's path filter has `**/*.go` in it, so a change to +Go code deploys the site. Narrow that filter back to `site/` and the badge in +the root README goes on reading a figure from whenever the site last changed, +with nothing failing to say so.