diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd10dd3..ab39748 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - uses: golangci/golangci-lint-action@v9 with: - version: v2.14.0 + version: latest # A module urga links, on any platform it ships for, may only have a # license internal/licenses allows. It runs after a lint failure too, so diff --git a/CLAUDE.md b/CLAUDE.md index 5057a9f..1b0fa81 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,8 +60,8 @@ over decisions and lessons, write the code here from scratch. - TDD, no exceptions. A failing test that names the behaviour comes first, then the smallest code that passes it. This holds for "simple" functions too. - `make check` (fmt, vet, lint, licenses, test, build) is green before every commit. - It needs `golangci-lint` of the version the Makefile names, the one CI uses, - and fails without it. + It needs `golangci-lint`, the latest release, which CI lints with, and fails + without it. - Conventional commits: `feat(ui): ...`, `fix(nomad): ...`, `chore: ...`. - No mention of other tools by name in code comments or commit messages. - Comments are short and say why, never what the line already says. Existing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c452761..3e86f83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,8 @@ make check # fmt, vet, lint, licenses, test, build make dist # the whole release, published nowhere ``` -`make lint` runs golangci-lint when it is installed; CI always runs it. +`make lint` needs golangci-lint, the latest release, which CI lints with; it +fails when golangci-lint is not installed. `make dist` needs goreleaser, the version named in `.tool-versions`. `make licenses` fails when a module urga links has a license outside the list in `internal/licenses`; `make notices` also writes `THIRD_PARTY_NOTICES`, diff --git a/Makefile b/Makefile index 20ceaf3..a6202b9 100644 --- a/Makefile +++ b/Makefile @@ -19,20 +19,13 @@ fmt: vet: go vet ./... -# The version CI lints with: another one finds other things. A test keeps -# the two the same. -GOLANGCI_LINT_VERSION := 2.14.0 - +# CI lints with the latest release. An older linter here passes what CI +# fails, so keep it updated. lint: @command -v golangci-lint >/dev/null 2>&1 || { \ - echo "golangci-lint is not installed: CI lints with v$(GOLANGCI_LINT_VERSION)"; \ + echo "golangci-lint is not installed: CI lints with the latest release"; \ exit 1; \ } - @installed=$$(golangci-lint version --short); \ - if [ "$$installed" != "$(GOLANGCI_LINT_VERSION)" ]; then \ - echo "golangci-lint $$installed is installed: CI lints with v$(GOLANGCI_LINT_VERSION)"; \ - exit 1; \ - fi golangci-lint run ./... # licenses fails when a module urga links, on any platform the release diff --git a/internal/buildconfig/makefile_test.go b/internal/buildconfig/makefile_test.go index 291d221..ed9defe 100644 --- a/internal/buildconfig/makefile_test.go +++ b/internal/buildconfig/makefile_test.go @@ -4,25 +4,12 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "testing" "github.com/stretchr/testify/require" ) -// ciLintVersion is the version of golangci-lint the CI workflow lints with. -func ciLintVersion(t *testing.T) string { - t.Helper() - - pinned := regexp.MustCompile(`golangci/golangci-lint-action@\S+\s+with:\s+version:\s*v(\S+)`) - - match := pinned.FindStringSubmatch(workflows(t)["ci.yml"]) - require.NotNil(t, match, "ci.yml pins no golangci-lint version") - - return match[1] -} - // lintWith runs `make lint` with nothing on the PATH but the golangci-lint // the test put there, if any: one that prints version for `version --short`. func lintWith(t *testing.T, version string) (string, error) { @@ -52,6 +39,12 @@ func lintWith(t *testing.T, version string) (string, error) { return string(out), err } +func TestCI_LintsWithTheLatestLinter(t *testing.T) { + // A pinned version falls behind, and then the linter on the machine of + // whoever runs make check finds what CI does not. + require.Regexp(t, `golangci/golangci-lint-action@\S+\s+with:\s+version:\s*latest\s`, workflows(t)["ci.yml"]) +} + func TestMakefile_LintFailsWithoutTheLinter(t *testing.T) { r := require.New(t) @@ -59,23 +52,13 @@ func TestMakefile_LintFailsWithoutTheLinter(t *testing.T) { out, err := lintWith(t, "") r.Error(err) r.Contains(out, "golangci-lint is not installed") - r.Contains(out, "v"+ciLintVersion(t)) + r.Contains(out, "latest") } -func TestMakefile_LintFailsWithAnotherVersion(t *testing.T) { +func TestMakefile_LintsWithTheInstalledVersion(t *testing.T) { r := require.New(t) - // Another version finds other problems, and CI fails on what passed here. out, err := lintWith(t, "2.0.0") - r.Error(err) - r.Contains(out, "2.0.0") - r.Contains(out, "v"+ciLintVersion(t)) -} - -func TestMakefile_LintsWithTheVersionCIUses(t *testing.T) { - r := require.New(t) - - out, err := lintWith(t, ciLintVersion(t)) r.NoError(err, out) r.Contains(out, "linted run ./...") } diff --git a/internal/nomad/logs.go b/internal/nomad/logs.go index 5e411b2..d1437a1 100644 --- a/internal/nomad/logs.go +++ b/internal/nomad/logs.go @@ -180,7 +180,11 @@ func (c *Client) logEnd(ctx context.Context, namespace string, alloc *api.Alloca end, known = LogMark{File: frame.File, Offset: frame.Offset}, true } - case <-errs: + case err := <-errs: + // The stream opened next reads the same log and shows why it + // failed. + _ = err + go drain(frames) return LogMark{}, false diff --git a/internal/ui/shell_test.go b/internal/ui/shell_test.go index bcf0c59..0575223 100644 --- a/internal/ui/shell_test.go +++ b/internal/ui/shell_test.go @@ -16,6 +16,7 @@ import ( tea "charm.land/bubbletea/v2" "github.com/charmbracelet/x/ansi" + "github.com/muesli/cancelreader" "github.com/stretchr/testify/require" "github.com/ingvarch/urga/internal/nomad" @@ -330,7 +331,8 @@ func TestShell_LeavesTheNextKeyToUrga(t *testing.T) { // The shell is closed, and nothing waits for a key on its behalf: left // waiting, it would take the first key typed afterwards. select { - case <-task.stopped: + case err := <-task.stopped: + r.ErrorIs(err, cancelreader.ErrCanceled) case <-time.After(time.Second): r.Fail("the closed shell still waits for a key") }