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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
13 changes: 3 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 8 additions & 25 deletions internal/buildconfig/makefile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -52,30 +39,26 @@ 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)

// A check that passes because the linter is missing checks nothing.
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 ./...")
}
6 changes: 5 additions & 1 deletion internal/nomad/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion internal/ui/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
}
Expand Down
Loading