Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,19 @@ jobs:
- run: go test -race ./...
- name: Build for Windows
run: GOOS=windows go build -o /dev/null ./cmd/proof

# Runs the action the way a server's CI would, against Babashka. As this
# isn't a release, the action builds proof from source. It's a test of
# the action rather than of Babashka, so only the eval checks run, and new
# checks Babashka fails don't break proof's CI.
action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: DeLaGuardo/setup-clojure@13.7.0
with:
bb: '1.13.226'
- uses: ./
with:
profile: babashka
args: -v -only eval.*
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Builds proof for every platform when a version is tagged (e.g. v0.1.0)
# and publishes the binaries in a GitHub release, whose notes are the
# version's section of the CHANGELOG.
name: Release

on:
push:
tags: ['v*']

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: stable
- name: Build
run: script/build-release "${GITHUB_REF_NAME#v}" dist
- name: Write the release notes
run: |
version="${GITHUB_REF_NAME#v}"
# Everything between the version's heading and the next one.
awk -v heading="## $version " 'index($0, heading) == 1 { on = 1; next } on && /^## / { exit } on' CHANGELOG.md > notes.md
if ! grep -q '[^[:space:]]' notes.md; then
echo "CHANGELOG.md has no section for $version"
exit 1
fi
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" --title "proof ${GITHUB_REF_NAME#v}" --notes-file notes.md dist/*
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Changelog

## main (unreleased)

Initial release.

### Features

- Checks for what clients need from a server's replies to `describe`,
unknown ops, sessions, `eval` and `stdin`, and for every message the
server sends (the `wire.*` checks).
- [#4](https://github.com/nrepl/proof/pull/4): Client profiles, which send
what CIDER, Calva, Conjure, vim-fireplace, REPLy and neorepl send while
connecting and evaluating code
([#6](https://github.com/nrepl/proof/pull/6),
[#8](https://github.com/nrepl/proof/pull/8)).
- Profiles for nREPL, Babashka, ClojureCLR, Basilisp, jank, dialtone and
repartee, which come with proof, and expected failures that can't go
stale.
- `proof matrix`, which builds a compatibility matrix out of the JSON
reports of several servers.
- [#1](https://github.com/nrepl/proof/pull/1): `proof proxy`, which checks
what a client sends to a server, with `-record` for starting a client
profile ([#5](https://github.com/nrepl/proof/pull/5)).
- [#2](https://github.com/nrepl/proof/pull/2): `proof serve`, a server for
the tests of clients that can behave like other servers
([#3](https://github.com/nrepl/proof/pull/3)).
- [#10](https://github.com/nrepl/proof/pull/10): Binaries for Linux, macOS
and Windows on every release, and a GitHub Action for running proof in the
CI of a server.
17 changes: 9 additions & 8 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ lookups and completions, plus requests that go wrong.

### Releases

- `proof version` takes the version from the build info, so a binary
installed with `go install ...@v0.1.0` knows its version.
- A CHANGELOG.
- Binaries for Linux, macOS and Windows on every release, so the CI of a
server doesn't need Go.
- A GitHub Action, so a server's workflow needs nothing but `uses:
- Done: `proof version` takes the version from the build info, so a
binary installed with `go install ...@v0.1.0` knows its version.
- Done: a CHANGELOG.
- Done: binaries for Linux, macOS and Windows on every release, so the
CI of a server doesn't need Go. The Release workflow builds them when
a version gets tagged.
- Done: a GitHub Action, so a server's workflow needs nothing but `uses:
nrepl/proof@v0.1.0` and the path to its profile.
- The CI section of [Usage](doc/usage.md) uses the release instead of
`@latest`.
- Done: the CI section of [Usage](doc/usage.md) uses the release instead
of `@latest`.

### interrupt

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ eventually.

## Quick Start

proof is a single binary. You can install it with Go 1.23 or newer:
proof is a single binary. You can download it from the
[releases](https://github.com/nrepl/proof/releases) or install it with Go
1.23 or newer:

```shell
$ go install github.com/nrepl/proof/cmd/proof@latest
Expand Down
77 changes: 77 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: proof
description: Check that an nREPL server works with the clients people use
branding:
icon: check-circle
color: green

inputs:
profile:
description: The profile of the server, or the name of one that comes with proof (e.g. babashka)
required: true
args:
description: More arguments for proof run, separated by spaces (e.g. -v -json report.json)
required: false
default: ''

runs:
using: composite
steps:
- name: Install proof
shell: bash
env:
GH_TOKEN: ${{ github.token }}
OS: ${{ runner.os }}
ARCH: ${{ runner.arch }}
run: |
bin="$RUNNER_TEMP/proof-bin"
mkdir -p "$bin"
os=$(echo "$OS" | tr '[:upper:]' '[:lower:]')
[ "$os" = macos ] && os=darwin
case "$ARCH" in
X64) arch=amd64 ;;
ARM64) arch=arm64 ;;
*) arch=unknown ;;
esac
# The action lives in .../_actions/OWNER/REPO/REF. Composite
# actions don't always get github.action_ref and
# github.action_repository (actions/runner#2473), so they come from
# the path instead.
path=${GITHUB_ACTION_PATH//\\//}
ref=$(basename "$path")
repo=$(basename "$(dirname "$(dirname "$path")")")/$(basename "$(dirname "$path")")

# A release (e.g. uses: nrepl/proof@v0.1.0) comes with binaries.
download() {
[[ "$ref" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]] && [ "$arch" != unknown ] || return 1
local name="proof_${ref#v}_${os}_${arch}" archive dir="$RUNNER_TEMP/proof-download"
archive="$name.tar.gz"
[ "$os" = windows ] && archive="$name.zip"
gh release download "$ref" --repo "$repo" --pattern "$archive" --pattern checksums.txt --dir "$dir" --clobber &&
(cd "$dir" && grep " $archive\$" checksums.txt | shasum -a 256 -c -) || return 1
if [ "$os" = windows ]; then
7z x -y -o"$dir" "$dir/$archive" > /dev/null && mv "$dir/$name/proof.exe" "$bin/"
else
tar -xzf "$dir/$archive" -C "$dir" && mv "$dir/$name/proof" "$bin/"
fi
}

# Anything else (a branch, a commit or the action in proof's own
# repository), or a release that can't be downloaded, gets built
# from source, which needs Go.
if ! download; then
if ! command -v go > /dev/null; then
echo "Building proof from $repo@$ref needs Go, so set it up (e.g. with actions/setup-go) before this step."
exit 1
fi
(cd "$GITHUB_ACTION_PATH" && go build -o "$bin/" ./cmd/proof)
fi
echo "$bin" >> "$GITHUB_PATH"
- name: Run proof
shell: bash
env:
PROFILE: ${{ inputs.profile }}
ARGS: ${{ inputs.args }}
run: |
# The arguments are split into words, but they're not file names.
set -f
proof run $ARGS "$PROFILE"
27 changes: 24 additions & 3 deletions cmd/proof/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"path"
"runtime/debug"
"strings"
"syscall"
"time"
Expand All @@ -23,7 +24,27 @@ import (
"github.com/nrepl/proof/internal/server"
)

const version = "0.1.0-dev"
// version is set for releases, with -ldflags "-X main.version=...".
var version string

// proofVersion returns the version of proof.
func proofVersion() string {
info, _ := debug.ReadBuildInfo()
return versionOf(version, info)
}

// versionOf returns the version a release set, or else the one go install
// recorded in the binary's build info (which may be nil). Builds that have
// neither (e.g. go run) are 0.0.0-dev, so the version still has numbers.
func versionOf(release string, info *debug.BuildInfo) string {
switch {
case release != "":
return release
case info != nil && info.Main.Version != "" && info.Main.Version != "(devel)":
return strings.TrimPrefix(info.Main.Version, "v")
}
return "0.0.0-dev"
}

const usage = `proof checks an nREPL server's compatibility with existing clients.

Expand Down Expand Up @@ -64,7 +85,7 @@ func main() {
case "list":
list(os.Stdout)
case "version", "--version", "-version":
fmt.Println("proof", version)
fmt.Println("proof", proofVersion())
case "help", "-h", "--help":
printUsage(os.Stdout)
default:
Expand Down Expand Up @@ -192,7 +213,7 @@ func run(args []string) int {
results := check.Run(env, selected, checks.WireRules())
outcome := check.Apply(results, p.ExpectedFailures, opts.only == "")

r := report.Run{Proof: version, Server: p.Name, Address: addr, Started: started, Results: results}
r := report.Run{Proof: proofVersion(), Server: p.Name, Address: addr, Started: started, Results: results}
report.Text(os.Stdout, r, opts.verbose)
for _, id := range outcome.Stale {
fmt.Printf("%s is listed in expected-failures but didn't fail; remove it from the profile\n", id)
Expand Down
26 changes: 26 additions & 0 deletions cmd/proof/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"runtime/debug"
"testing"
)

func TestVersion(t *testing.T) {
built := func(v string) *debug.BuildInfo { return &debug.BuildInfo{Main: debug.Module{Version: v}} }
cases := []struct {
release string
info *debug.BuildInfo
want string
}{
{"0.1.0", built("v0.2.0"), "0.1.0"},
{"", built("v0.2.0"), "0.2.0"},
{"", built("v0.0.0-20261023130420-411883581cfd"), "0.0.0-20261023130420-411883581cfd"},
{"", built("(devel)"), "0.0.0-dev"},
{"", nil, "0.0.0-dev"},
}
for _, c := range cases {
if got := versionOf(c.release, c.info); got != c.want {
t.Errorf("versionOf(%q, %v) = %q, want %q", c.release, c.info, got, c.want)
}
}
}
2 changes: 1 addition & 1 deletion cmd/proof/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func proxyUntil(ctx context.Context, args []string, stdout, stderr io.Writer, li

<-ctx.Done()
fmt.Fprintln(stderr)
r := report.Run{Proof: version, Server: "client traffic to " + name, Address: upstream, Started: started}
r := report.Run{Proof: proofVersion(), Server: "client traffic to " + name, Address: upstream, Started: started}
return gradeClients(stdout, stderr, r, px.Stop(time.Second), o.clientOptions, srv)
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/proof/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func serveUntil(ctx context.Context, args []string, stdout, stderr io.Writer, li
}
shown = append([]string{"like " + name}, shown...)
}
srv, err := serve.Listen(o.listen, version, scenarios)
srv, err := serve.Listen(o.listen, proofVersion(), scenarios)
if err != nil {
fmt.Fprintln(stderr, "proof:", err)
return 2
Expand All @@ -75,6 +75,6 @@ func serveUntil(ctx context.Context, args []string, stdout, stderr io.Writer, li

<-ctx.Done()
fmt.Fprintln(stderr)
r := report.Run{Proof: version, Server: "client traffic to " + name, Address: srv.Addr(), Started: started}
r := report.Run{Proof: proofVersion(), Server: "client traffic to " + name, Address: srv.Addr(), Started: started}
return gradeClients(stdout, stderr, r, srv.Stop(time.Second), o.clientOptions, nil)
}
23 changes: 22 additions & 1 deletion doc/hacking.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,34 @@ links using it still point to the right lines.

## Continuous Integration

proof uses a couple of GitHub Actions workflows:
proof uses a few GitHub Actions workflows:

- CI checks the formatting, runs `go vet` and the tests on Linux and
macOS (with both the oldest supported Go version and the latest
stable one) and makes sure proof builds on Windows.
- CI also runs proof's GitHub Action (`action.yml`) against Babashka,
the way a server's CI would use it.
- Compatibility runs all the profiles against their servers every day
(and on every push to `main`) and builds the compatibility matrix in
the job summary. As servers failing checks is perfectly normal, this
workflow fails only when proof itself can't do its job (e.g. a server
can't be installed or doesn't start).
- Release builds the binaries for every platform when a version gets
tagged and publishes them in a GitHub release.

## Releasing

Releasing a new version of proof is a very simple process:

1. Make sure CI and the Compatibility workflow are green.
2. Turn the `main (unreleased)` section of the CHANGELOG into a section
for the new version (e.g. `## 0.2.0 (2027-01-15)`).
3. Update the version in the examples in [Usage](usage.md) (e.g.
`nrepl/proof@v0.2.0`).
4. Commit the changes ("Release 0.2.0"), tag the commit (`v0.2.0`) and
push both.

The Release workflow takes it from there. It builds the binaries with
`script/build-release` and publishes them in a GitHub release, whose
notes are the version's section of the CHANGELOG. If you edit that
section later, update the notes of the release as well.
Loading
Loading