~bigbes/sr-ht-ecore

ref: 89fa694cbf548ecd642b8101825b7a49b48f975a sr-ht-ecore/Makefile -rw-r--r-- 9.1 KiB
89fa694c — Eugene Blikh metapat: the meta.sr.ht PAT plane a federated endpoint needs a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# sr-ht-ecore — build scaffolding (bench.sr.ht / cov.sr.ht family style).
#
# This repository is a LIBRARY: no cmd/, no daemon, no stylesheet, no apk. There
# is nothing to install and nothing to package, so the targets a service's
# Makefile carries for those — css, keygen, install, check-version — have no
# meaning here and are absent rather than stubbed.
#
# What is left is what the seven services pinning this module actually need from
# it: that it compiles, that it is formatted and vets clean, that its tests pass,
# and that its coverage profile and its benchmarks come out in the shape
# .build.yml uploads to cov.srht.bigb.es and bench.srht.bigb.es. That is what the
# targets below are, and .build.yml calls them by name — the Go command lines
# live here, once, instead of being copied into the manifest.

GO?=go
GOFMT?=gofmt

# PKG is what to run and COUNT is `go test -count`, both variables so that
# `make test-race COUNT=10 PKG=./bearer/` needs no retyping of the flags that
# get forgotten when a command is retyped by hand.
PKG?=./...
COUNT?=1

# TEST_TIMEOUT is the per-package ceiling handed to `go test`, and -timeout is
# per test binary, so what it has to cover is the slowest package and not the
# suite. `make test` does not need it: 11 s wall for the whole tree on an M4
# Pro, measured with go1.25 at -count=1, no package over 10 s. `make bench`
# is where it earns its keep — measured on that same machine at BENCH_COUNT=10,
# 6 min 06 s wall over 29 benchmark cases, of which grants took 96.4 s, bearer
# 78.5 s and csrf 73.0 s. A sixth of the toolchain's ten-minute default is
# comfortable on a laptop and not obviously comfortable on a builds.sr.ht VM,
# which is a slower machine running the same fixed number of iterations.
# Twenty minutes is the number the sibling services carry, for the same reason:
# what it prevents — `panic: test timed out` over three hundred lines of
# goroutine dump — reads like a hang in the code rather than like a budget.
TEST_TIMEOUT?=20m

# BENCH_COUNT is `go test -count` for the `bench` target. Ten is bench.sr.ht's
# own number (its SPEC ch. 5): a confidence interval becomes finite at six
# repetitions and a comparison becomes significant at four, so anything under
# six uploads points the service can only mark "low n". It is a variable so a
# laptop can say `make bench BENCH_COUNT=1` when it only wants to know that the
# benchmarks still run.
BENCH_COUNT?=10

# Where `make cover` writes the profile. .build.yml overrides it to $HOME —
# that is where builds.sr.ht's artifacts: looks — and the default is the
# checkout, which is what a developer means by `make cover`. It lands untracked
# there; `make clean` is what removes it, and .gitignore does not list it.
COVERPROFILE?=cover.out

# The sibling services default to `build`, and each of them says in the same
# breath that it was `help` while there was nothing to build. Here there is
# nothing to build — `build` is a compile check, not a product — so the default
# stays where theirs started, and a bare `make` spends no time proving something
# the author did not ask about.
.DEFAULT_GOAL := help

help:
	@echo "targets:"
	@echo "  help       this list (the default target)"
	@echo "  build      compile every package: go build \$$(PKG)"
	@echo "  test       go test \$$(PKG) with an explicit -timeout"
	@echo "  test-race  the same under -race and repeatable:"
	@echo "               make test-race COUNT=10 PKG=./bearer/"
	@echo "  cover      test with -covermode=atomic into \$$(COVERPROFILE) and"
	@echo "             print the total; the profile .build.yml POSTs to cov.sr.ht"
	@echo "             (COVERPROFILE=$(COVERPROFILE))"
	@echo "  bench      go test -bench=. \$$(PKG) as benchfmt on stdout, the format"
	@echo "             .build.yml POSTs to bench.sr.ht (BENCH_COUNT=$(BENCH_COUNT));"
	@echo "             everything that is not benchfmt is filtered out — see"
	@echo "             BENCH_FILTER, and 'make bench BENCH_FILTER=.' for the raw run"
	@echo "  vet        go vet \$$(PKG)"
	@echo "  fmt        rewrite this module's Go files with gofmt"
	@echo "  check-fmt  the same as a refusal: fail if gofmt would rewrite anything"
	@echo "             (this is the one CI runs; fmt writes, gates must not)"
	@echo "  clean      remove \$$(COVERPROFILE)"

# There is no binary to link, so this is a compile check and nothing else. It is
# still worth a target: `go vet` compiles too, but a build failure reported by
# vet reads like a vet finding.
build:
	$(GO) build $(PKG)

test:
	$(GO) test -timeout $(TEST_TIMEOUT) $(PKG)

# The same suite under the race detector, repeatable. Half of what this library
# holds is middleware sitting on every request of every service — chimw's
# logger, middleware's chain, mcphttp's cache — so the run that matters for it
# is this one, not `test`.
test-race:
	$(GO) test -race -count=$(COUNT) -timeout $(TEST_TIMEOUT) $(PKG)

# -covermode=atomic and not the default `set`: `set` records whether a statement
# ran, atomic records how often, and cov.sr.ht reads hit counts. The profile is
# the artifact; the printed total is a convenience.
#
# The `test -s` in the middle is not decoration. A profile that came out empty
# is still a valid file, and uploading it would report success over a report
# that covers nothing — the same failure the .build.yml bench task greps
# against.
cover:
	$(GO) test -covermode=atomic -coverprofile=$(COVERPROFILE) -timeout $(TEST_TIMEOUT) $(PKG)
	@test -s $(COVERPROFILE) || { echo "$(COVERPROFILE) is empty" >&2; exit 1; }
	$(GO) tool cover -func=$(COVERPROFILE) | tail -1

# The benchmarks in the form bench.sr.ht ingests: benchfmt on stdout, which is
# what `go test -bench` writes. -run='^$$' keeps the tests out of it — their
# output is noise the parser skips and their runtime is time added to a run
# whose point is the benchmarks. -benchmem because B/op and allocs/op are half
# of what a middleware benchmark means and they cost nothing to collect.
#
# BENCH_FILTER is the part that is not boilerplate, and it is here because a
# benchmark's own stdout is not only the benchmark. `go test` merges the test
# binary's stderr into its stdout, so anything the code under test logs during a
# measured loop lands in the middle of the benchfmt document. Measured on this
# tree at BENCH_COUNT=10: bearer's invalid-token case makes sr-ht-core's
# auth.DecodeBearerToken call log.Printf once per iteration, and an unfiltered
# run was 901 MB and 9.5 million lines deep — around the 290 result lines a full
# run has — when it was stopped, a third of the way through. Filtered, the same
# run is 340 lines and 31 KB. benchfmt ignores lines it cannot parse, so the
# unfiltered file would upload "successfully": a POST of a gigabyte of noise,
# whatever bench.sr.ht's byte budget makes of it.
#
# So the target emits the benchfmt grammar (its configuration lines and its
# result lines) plus the words that say a run failed, and nothing else. The
# filter is a variable: `make bench BENCH_FILTER=.` is the same run with every
# line of it. It is a defence and not a cure — the cure is for a benchmark that
# provokes logging to silence the logger first, the way middleware's discardLog
# and chimw's io.Discard handler already do.
#
# The exit status travels in a file because the grep is a pipe: taking the
# pipeline's status would report grep's opinion of the output instead of whether
# the benchmarks ran, which is the failure mode that makes a red suite read
# green.
BENCH_FILTER?=^(goos|goarch|pkg|cpu): |^Benchmark|^(PASS|FAIL|ok|--- |panic: )

bench:
	@echo "+ $(GO) test -run='^$$' -bench=. -benchmem -count=$(BENCH_COUNT) -timeout $(TEST_TIMEOUT) $(PKG)" >&2
	@st=$$(mktemp); \
	{ $(GO) test -run='^$$' -bench=. -benchmem -count=$(BENCH_COUNT) \
		-timeout $(TEST_TIMEOUT) $(PKG) 2>&1; echo $$? > "$$st"; } \
		| grep -E '$(BENCH_FILTER)' || true; \
	rc=$$(cat "$$st"); rm -f "$$st"; \
	test -n "$$rc" || rc=1; \
	exit "$$rc"

vet:
	$(GO) vet $(PKG)

# GOFMT_DIRS is `go list`'s answer and not a literal `.`, and that is the whole
# point of the two targets below. The repository convention here is to create
# git worktrees under .worktrees/<branch>, so a plain `gofmt -l .` at the root
# would walk into another checkout of this same module and report — or, for
# `fmt`, rewrite — files that are not in this working tree at all. A worktree
# carries its own go.mod, so it is a different module to `go list`, and ./...
# never leaves this one.
GOFMT_DIRS=$$($(GO) list -f '{{.Dir}}' $(PKG))

fmt:
	$(GOFMT) -l -w $(GOFMT_DIRS)

# `gofmt -l` prints the offending files and STILL EXITS 0, so it passes any &&
# chain and any CI task that only looks at the status. This target is that check
# written so a failure stops the build, and it is the one .build.yml calls; fmt
# above rewrites and must never be what a gate runs.
check-fmt:
	@out=$$($(GOFMT) -l $(GOFMT_DIRS)); \
	if [ -n "$$out" ]; then \
		echo "gofmt would rewrite:" >&2; \
		echo "$$out" >&2; \
		exit 1; \
	fi; \
	echo "gofmt: clean"

# Only the profile: `bench` writes to stdout and creates no file of its own, so
# there is nothing else here to remove.
clean:
	rm -f $(COVERPROFILE)

.PHONY: help build test test-race cover bench vet fmt check-fmt clean