Repository: containerd/nerdctl

Issue:
Stats panic (`send on closed channel`)
### Description

Seen on CI. Stats panic sending to a closed channel.

### Steps to reproduce the issue

_No response_

### Describe the results you received and expected

```
=== RUN   TestStats/container_stats
=== PAUSE TestStats/container_stats
=== CONT  TestStats/container_stats
    container_stats_test.go:110: 
    container_stats_test.go:110: ======================== Pre-test cleanup ========================
    container_stats_test.go:110: 
    container_stats_test.go:110: ======================== Test setup ========================
    container_stats_test.go:110: 
    container_stats_test.go:110: ======================== Test Run ========================
    command.go:113: /usr/local/bin/nerdctl --namespace=nerdctl-test container stats --no-stream --no-trunc
    command.go:113: assertion failed: expect.ExitCode is not result.ExitCode: Expected exit code: 0
        
        Command:  /usr/local/bin/nerdctl --namespace=nerdctl-test container stats --no-stream --no-trunc
        ExitCode: 2
        Error:    exit status 2
        Stdout:   CONTAINER ID                                                       NAME                                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O     PIDS
        56c2e9ebefbb3836c2bf0576fc2b7dbdd35d886e04d2cf799673d29936d767ef   testexectty-3d80652a                 0.00%     352KiB / 15.62GiB     0.00%     7.12kB / 612B   0B / 0B       1
        ec83950c27fe969597be9c97f9f8c8bd71383de4474139adff9317b0f6e7b37d   teststats-memlimited-2f829aaf        0.00%     352KiB / 1GiB         0.03%     9.52kB / 612B   0B / 0B       1
        ed8d898251f830ac5aeb36e4807f97d84792d422c97b43cd6d69034083dec2f4   teststats-container-8514[604](https://github.com/containerd/nerdctl/actions/runs/14100070551/job/39494696009?pr=4041#step:7:605)0         0.00%     344KiB / 15.62GiB     0.00%     10kB / 612B     0B / 0B       1
        f9b366abcd25db264bceabf7e9949117f8a0d1379ba871f9c645d27ffe6a6bee   testsharednetworkwithnone-7c41c3a8   0.00%     3.621MiB / 15.62GiB   0.02%     0B / 0B         0B / 12.3kB   5
        
        Stderr:   panic: send on closed channel
        
        goroutine 27 [running]:
        github.com/containerd/nerdctl/v2/pkg/cmd/container.Stats.func1(0xc00004a540, 0xc00004a5b0)
        	/go/src/github.com/containerd/nerdctl/pkg/cmd/container/stats.go:125 +0xb5
        created by github.com/containerd/nerdctl/v2/pkg/cmd/container.Stats in goroutine 1
        	/go/src/github.com/containerd/nerdctl/pkg/cmd/container/stats.go:207 +0x69b
        
        Env:
        HOSTNAME=3ace26e5dae4
        MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
        SYSTEMD_EXEC_PID=80
        container=docker
        HOME=/root
        LANG=C.UTF-8
        MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/docker-entrypoint.service/memory.pressure
        INVOCATION_ID=b7f9ea7c79654b64ae96f68ae8a7552d
        TERM=xterm
        USER=root
        SHLVL=3
        CGO_ENABLED=0
        _=/usr/local/bin/gotestsum
        PATH=/usr/local/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
        ***
        DOCKER_CONFIG=/tmp/TestStatscontainer_stats3074483677/001
        NERDCTL_TOML=/tmp/TestStatscontainer_stats3074483677/001/nerdctl.toml
    case.go:180: 
    case.go:181: ======================== Post-test cleanup ========================
--- FAIL: TestStats/container_stats (1.04s)

```


### What version of nerdctl are you using?

main

### Are you using a variant of nerdctl? (e.g., Rancher Desktop)

None

### Host information

_No response_

Discussion:
@apostasie: @AkihiroSuda +`help-wanted`?

Anyone around wants to take this? (panic is not nice, we should really fix it)
@fahedouch maybe if you have free cycles?

Solution patch:
diff --git a/go.mod b/go.mod
index d33d9966..61af0752 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,7 @@ require (
 	github.com/containerd/imgcrypt/v2 v2.0.1
 	github.com/containerd/log v0.1.0
 	github.com/containerd/nerdctl/mod/tigron v0.0.0
-	github.com/containerd/nydus-snapshotter v0.15.0
+	github.com/containerd/nydus-snapshotter v0.15.1
 	github.com/containerd/platforms v1.0.0-rc.1
 	github.com/containerd/stargz-snapshotter v0.16.3
 	github.com/containerd/stargz-snapshotter/estargz v0.16.3
diff --git a/go.sum b/go.sum
index eb86a38a..5af41bd6 100644
--- a/go.sum
+++ b/go.sum
@@ -49,8 +49,8 @@ github.com/containerd/imgcrypt/v2 v2.0.1 h1:gQcmeCKA97fAl0wlpq0itSY/PagFBsn4/mlK
 github.com/containerd/imgcrypt/v2 v2.0.1/go.mod h1:/qIJL8nxzdzMA2n5iYyyuIY36KfoVQWmgTWdfVtyebM=
 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
 github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8=
-github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw=
+github.com/containerd/nydus-snapshotter v0.15.1 h1:huPj2d8J1BEx6mjm6h72BCo1kY5lTrfatnnujzpu6BA=
+github.com/containerd/nydus-snapshotter v0.15.1/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM=
 github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
 github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
 github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
diff --git a/mod/tigron/.golangci.yml b/mod/tigron/.golangci.yml
index 66e0b8d2..aa2f7f1a 100644
--- a/mod/tigron/.golangci.yml
+++ b/mod/tigron/.golangci.yml
@@ -1,54 +1,63 @@
----
-output:
-  sort-results: true
-
-issues:
-  max-issues-per-linter: 0
-  max-same-issues: 0
+version: "2"
 
 run:
-  concurrency: 0
-  timeout: 5m
   issues-exit-code: 2
-  tests: true
   modules-download-mode: readonly
   allow-parallel-runners: true
   allow-serial-runners: true
 
+issues:
+  max-issues-per-linter: 0
+  max-same-issues: 0
+
 linters:
-  disable-all: false
-  enable-all: true
+  default: all
   disable:
-    # Opting-out
-    - nonamedreturns  # named returns are occasionally useful
-    - exhaustruct  # does not serve much of a purpose
-    - funlen  # not interested
-    - cyclop  # not interested much
-    - godox  # having these are useful
-    # Duplicating
-    - gci  # we use go-imports instead
-    # Deprecated
-    - tenv
-    # TODO: Temporarily out until we wrap up all of them
-#    - wrapcheck
+    - cyclop
+    - exhaustruct
+    - funlen
+    - godox
+    - nonamedreturns
+  settings:
+    depguard:
+      rules:
+        main:
+          files:
+            - $all
+          allow:
 
-linters-settings:
-  staticcheck:
-    checks:
-      - "all"
+            - $gostd
+            - github.com/containerd/nerdctl/mod/tigron
+            - github.com/creack/pty
+            - golang.org/x/sync
+            - golang.org/x/term
+            - gotest.tools/v3
+    staticcheck:
+      checks:
+        - all
+  exclusions:
+    generated: disable
 
-  depguard:
-    rules:
-      main:
-        files:
-          - "$all"
-        allow:
-          - $gostd
-          - "github.com/containerd/nerdctl/mod/tigron"
-          # WATCHOUT! https://github.com/OpenPeeDeeP/depguard/issues/108
-          # Currently, depguard will fail detecting any dependency starting with a standard package name as third-party.
-          # Thus, the following three are allowed provisionally, though currently not "necessary".
-          - "golang.org/x/sync"
-          - "golang.org/x/term"
-          - "gotest.tools"
-          - "github.com/creack/pty"
+formatters:
+  settings:
+    gci:
+      sections:
+        - standard
+        - default
+        - prefix(github.com/containerd)
+        - localmodule
+      no-inline-comments: true
+      no-prefix-comments: true
+      custom-order: true
+    gofumpt:
+      extra-rules: true
+    golines:
+      max-len: 100
+      tab-len: 4
+      shorten-comments: true
+  enable:
+    - gci
+    - gofumpt
+    - golines
+  exclusions:
+    generated: disable
diff --git a/mod/tigron/Makefile b/mod/tigron/Makefile
index 681dbc3f..b534581b 100644
--- a/mod/tigron/Makefile
+++ b/mod/tigron/Makefile
@@ -57,16 +57,16 @@ endef
 ##########################
 # High-level tasks definitions
 ##########################
-lint: lint-go-all lint-imports lint-yaml lint-shell lint-commits lint-headers lint-mod lint-licenses-all
+lint: lint-go-all lint-yaml lint-shell lint-commits lint-headers lint-mod lint-licenses-all
 test: test-unit test-unit-race test-unit-bench
 unit: test-unit test-unit-race test-unit-bench
-fix: fix-mod fix-imports fix-go-all
+fix: fix-mod fix-go-all
 
 ##########################
 # Linting tasks
 ##########################
 lint-go:
-	$(call title, $@)
+	$(call title, $@: $(GOOS))
 	@cd $(MAKEFILE_DIR) \
 		&& golangci-lint run $(VERBOSE_FLAG_LONG) ./...
 	$(call footer, $@)
@@ -80,12 +80,6 @@ lint-go-all:
 		&& GOOS=windows make lint-go
 	$(call footer, $@)
 
-lint-imports:
-	$(call title, $@)
-	@cd $(MAKEFILE_DIR) \
-		&& goimports-reviser -recursive -list-diff -set-exit-status -output stdout -company-prefixes "$(ORG_PREFIXES)"  ./...
-	$(call footer, $@)
-
 lint-yaml:
 	$(call title, $@)
 	@cd $(MAKEFILE_DIR) \
@@ -115,10 +109,15 @@ lint-mod:
 		&& go mod tidy --diff
 	$(call footer, $@)
 
+# FIXME: go-licenses cannot find LICENSE from root of repo when submodule is imported:
+# https://github.com/google/go-licenses/issues/186
+# This is impacting gotest.tools
 lint-licenses:
-	$(call title, $@)
+	$(call title, $@: $(GOOS))
 	@cd $(MAKEFILE_DIR) \
-		&& ./hack/make-lint-licenses.sh
+		&& go-licenses check --include_tests --allowed_licenses=Apache-2.0,BSD-2-Clause,BSD-3-Clause,MIT,MPL-2.0 \
+		  --ignore gotest.tools \
+		  ./...
 	$(call footer, $@)
 
 lint-licenses-all:
@@ -134,7 +133,7 @@ lint-licenses-all:
 # Automated fixing tasks
 ##########################
 fix-go:
-	$(call title, $@)
+	$(call title, $@: $(GOOS))
 	@cd $(MAKEFILE_DIR) \
 		&& golangci-lint run --fix
 	$(call footer, $@)
@@ -148,12 +147,6 @@ fix-go-all:
 		&& GOOS=windows make fix-go
 	$(call footer, $@)
 
-fix-imports:
-	$(call title, $@)
-	@cd $(MAKEFILE_DIR) \
-		&& goimports-reviser -company-prefixes $(ORG_PREFIXES) ./...
-	$(call footer, $@)
-
 fix-mod:
 	$(call title, $@)
 	@cd $(MAKEFILE_DIR) \
@@ -171,18 +164,16 @@ up:
 ##########################
 install-dev-tools:
 	$(call title, $@)
-	# golangci: v1.64.5
-	# git-validation: main from 2023/11
-	# ltag: v0.2.5
-	# go-licenses: v2.0.0-alpha.1
-	# goimports-reviser: v3.9.0
+	# golangci: v2.0.2 (2024-03-26)
+	# git-validation: main (2025-02-25)
+	# ltag: main (2025-03-04)
+	# go-licenses: v2.0.0-alpha.1 (2024-06-27)
 	@cd $(MAKEFILE_DIR) \
-		&& go install github.com/golangci/golangci-lint/cmd/golangci-lint@0a603e49e5e9870f5f9f2035bcbe42cd9620a9d5 \
-		&& go install github.com/vbatts/git-validation@679e5cad8c50f1605ab3d8a0a947aaf72fb24c07 \
-		&& go install github.com/kunalkushwaha/ltag@b0cfa33e4cc9383095dc584d3990b62c95096de0 \
-		&& go install github.com/google/go-licenses/v2@d01822334fba5896920a060f762ea7ecdbd086e8 \
-		&& go install github.com/incu6us/goimports-reviser/v3@698f92d226d50a01731ca8551993ebc1bb7fc788
-	@echo "Remember to add GOROOT/bin to your path"
+		&& go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@2b224c2cf4c9f261c22a16af7f8ca6408467f338 \
+		&& go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \
+		&& go install github.com/containerd/ltag@66e6a514664ee2d11a470735519fa22b1a9eaabd \
+		&& go install github.com/google/go-licenses/v2@d01822334fba5896920a060f762ea7ecdbd086e8
+	@echo "Remember to add \$$HOME/go/bin to your path"
 	$(call footer, $@)
 
 ##########################
@@ -200,7 +191,7 @@ test-unit-bench:
 
 test-unit-race:
 	$(call title, $@)
-	@go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/... -race
+	@CGO_ENABLED=1 go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/... -race
 	$(call footer, $@)
 
 .PHONY: \
@@ -210,6 +201,6 @@ test-unit-race:
 	up \
 	unit \
 	install-dev-tools \
-	lint-commits lint-go lint-go-all lint-headers lint-imports lint-licenses lint-licenses-all lint-mod lint-shell lint-yaml \
-	fix-go fix-go-all fix-imports fix-mod \
-	test-unit test-unit-race test-unit-bench
+	lint-commits lint-go lint-go-all lint-headers lint-licenses lint-licenses-all lint-mod lint-shell lint-yaml \
+	fix-go fix-go-all fix-mod \
+	test-unit test-unit-race test-unit-bench
\ No newline at end of file
diff --git a/mod/tigron/expect/comparators.go b/mod/tigron/expect/comparators.go
index dde002db..9ccaadef 100644
--- a/mod/tigron/expect/comparators.go
+++ b/mod/tigron/expect/comparators.go
@@ -30,7 +30,7 @@ import (
 // All can be used as a parameter for expected.Output to group a set of comparators.
 func All(comparators ...test.Comparator) test.Comparator {
 	//nolint:thelper
-	return func(stdout string, info string, t *testing.T) {
+	return func(stdout, info string, t *testing.T) {
 		t.Helper()
 
 		for _, comparator := range comparators {
@@ -43,17 +43,18 @@ func All(comparators ...test.Comparator) test.Comparator {
 // is found contained in the output.
 func Contains(compare string) test.Comparator {
 	//nolint:thelper
-	return func(stdout string, info string, t *testing.T) {
+	return func(stdout, info string, t *testing.T) {
 		t.Helper()
 		assert.Check(t, strings.Contains(stdout, compare),
 			fmt.Sprintf("Output does not contain: %q", compare)+info)
 	}
 }
 
-// DoesNotContain is to be used for expected.Output to ensure a comparison string is NOT found in the output.
+// DoesNotContain is to be used for expected.Output to ensure a comparison string is NOT found in
+// the output.
 func DoesNotContain(compare string) test.Comparator {
 	//nolint:thelper
-	return func(stdout string, info string, t *testing.T) {
+	return func(stdout, info string, t *testing.T) {
 		t.Helper()
 		assert.Check(t, !strings.Contains(stdout, compare),
 			fmt.Sprintf("Output does contain: %q", compare)+info)
@@ -63,7 +64,7 @@ func DoesNotContain(compare string) test.Comparator {
 // Equals is to be used for expected.Output to ensure it is exactly the output.
 func Equals(compare string) test.Comparator {
 	//nolint:thelper
-	return func(stdout string, info string, t *testing.T) {
+	return func(stdout, info string, t *testing.T) {
 		t.Helper()
 		assert.Equal(t, compare, stdout, info)
 	}
@@ -73,7 +74,7 @@ func Equals(compare string) test.Comparator {
 // Provisional - expected use, but have not seen it so far.
 func Match(reg *regexp.Regexp) test.Comparator {
 	//nolint:thelper
-	return func(stdout string, info string, t *testing.T) {
+	return func(stdout, info string, t *testing.T) {
 		t.Helper()
 		assert.Check(t, reg.MatchString(stdout), "Output does not match: "+reg.String(), info)
 	}
diff --git a/mod/tigron/expect/consts.go b/mod/tigron/expect/consts.go
index 670e12b4..2abab223 100644
--- a/mod/tigron/expect/consts.go
+++ b/mod/tigron/expect/consts.go
@@ -17,8 +17,13 @@
 package expect
 
 const (
-	ExitCodeSuccess     = 0
+	// ExitCodeSuccess will ensure that the command effectively ran returned with exit code zero.
+	ExitCodeSuccess = 0
+	// ExitCodeGenericFail will verify that the command ran and exited with a non-zero error code.
+	// This does NOT include timeouts, cancellation, or signals.
 	ExitCodeGenericFail = -1
-	ExitCodeNoCheck     = -2
-	ExitCodeTimeout     = -3
+	// ExitCodeNoCheck does not enforce any check at all on the function.
+	ExitCodeNoCheck = -2
+	// ExitCodeTimeout verifies that the command was cancelled on timeout.
+	ExitCodeTimeout = -3
 )
diff --git a/mod/tigron/expect/doc.go b/mod/tigron/expect/doc.go
new file mode 100644
index 00000000..962a92ce
--- /dev/null
+++ b/mod/tigron/expect/doc.go
@@ -0,0 +1,19 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// Package expect provides a set of simple concrete test.Comparator implementations to use by tests
+// on stdout, along with exit code expectations.
+package expect
diff --git a/mod/tigron/hack/make-lint-licenses.sh b/mod/tigron/hack/make-lint-licenses.sh
deleted file mode 100755
index 23bdca51..00000000
--- a/mod/tigron/hack/make-lint-licenses.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-#   Copyright The containerd Authors.
-
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-
-#       http://www.apache.org/licenses/LICENSE-2.0
-
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-# shellcheck disable=SC2034,SC2015
-set -o errexit -o errtrace -o functrace -o nounset -o pipefail
-
-# FIXME: go-licenses cannot find LICENSE from root of repo when submodule is imported:
-# https://github.com/google/go-licenses/issues/186
-# This is impacting gotest.tools
-# go-licenses is also really broken right now wrt to stdlib: https://github.com/google/go-licenses/issues/244
-# workaround taken from the awesome folks at Pulumi: https://github.com/pulumi/license-check-action/pull/3
-go-licenses check --include_tests --allowed_licenses=Apache-2.0,BSD-2-Clause,BSD-3-Clause,MIT,MPL-2.0 \
-	  --ignore gotest.tools \
-	  --ignore "$(go list std | awk 'NR > 1 { printf(",") } { printf("%s",$0) } END { print "" }')" \
-	  ./...
-
-printf "WARNING: you need to manually verify licenses for:\n- gotest.tools\n"
diff --git a/mod/tigron/require/doc.go b/mod/tigron/require/doc.go
new file mode 100644
index 00000000..f816c257
--- /dev/null
+++ b/mod/tigron/require/doc.go
@@ -0,0 +1,19 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// Package require provides a set of concrete test.Requirements to express the need for a specific
+// architecture, OS, or binary, along with Not() and All() which allow Requirements composition.
+package require
diff --git a/mod/tigron/require/requirement.go b/mod/tigron/require/requirement.go
index 4f110a5b..76c0b95f 100644
--- a/mod/tigron/require/requirement.go
+++ b/mod/tigron/require/requirement.go
@@ -24,6 +24,7 @@ import (
 	"github.com/containerd/nerdctl/mod/tigron/test"
 )
 
+// Binary requires a certain binary to be present in the PATH.
 func Binary(name string) *test.Requirement {
 	return &test.Requirement{
 		Check: func(_ test.Data, _ test.Helpers) (bool, string) {
@@ -39,45 +40,40 @@ func Binary(name string) *test.Requirement {
 	}
 }
 
+// OS requires a specific operating system (matched against runtime.GOOS).
 func OS(os string) *test.Requirement {
 	return &test.Requirement{
 		Check: func(_ test.Data, _ test.Helpers) (bool, string) {
-			mess := fmt.Sprintf("current operating system is %q", runtime.GOOS)
-			ret := true
-			if runtime.GOOS != os {
-				ret = false
-			}
-
-			return ret, mess
+			return runtime.GOOS == os, fmt.Sprintf("current operating system is %q", runtime.GOOS)
 		},
 	}
 }
 
+// Arch requires a specific CPU architecture (matched against runtime.GOARCH).
 func Arch(arch string) *test.Requirement {
 	return &test.Requirement{
 		Check: func(_ test.Data, _ test.Helpers) (bool, string) {
-			mess := fmt.Sprintf("current architecture is %q", runtime.GOARCH)
-			ret := true
-			if runtime.GOARCH != arch {
-				ret = false
-			}
-
-			return ret, mess
+			return runtime.GOARCH == arch, fmt.Sprintf("current architecture is %q", runtime.GOARCH)
 		},
 	}
 }
 
 //nolint:gochecknoglobals
 var (
-	Amd64   = Arch("amd64")
-	Arm64   = Arch("arm64")
+	// Amd64 requires an intel x86_64 CPU.
+	Amd64 = Arch("amd64")
+	// Arm64 requires an ARM CPU.
+	Arm64 = Arch("arm64")
+	// Windows requires the OS to be Windows.
 	Windows = OS("windows")
-	Linux   = OS("linux")
-	Darwin  = OS("darwin")
+	// Linux requires the OS to be Linux.
+	Linux = OS("linux")
+	// Darwin requires the OS to be macOS.
+	Darwin = OS("darwin")
 )
 
-// NOTE: Not will always ignore any setup and cleanup inside the wrapped requirement.
-
+// Not will negate another requirement
+// Note that it will always *ignore* any setup and cleanup inside the wrapped requirement.
 func Not(requirement *test.Requirement) *test.Requirement {
 	return &test.Requirement{
 		Check: func(data test.Data, helpers test.Helpers) (bool, string) {
@@ -88,6 +84,7 @@ func Not(requirement *test.Requirement) *test.Requirement {
 	}
 }
 
+// All combines several other requirements together.
 func All(requirements ...*test.Requirement) *test.Requirement {
 	return &test.Requirement{
 		Check: func(data test.Data, helpers test.Helpers) (bool, string) {
diff --git a/mod/tigron/utils/doc.go b/mod/tigron/utils/doc.go
new file mode 100644
index 00000000..929fd705
--- /dev/null
+++ b/mod/tigron/utils/doc.go
@@ -0,0 +1,19 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// Package utils provides generic helpers that are regularly useful for a range of test authors.
+// TODO: question the usefulness of this and whether this should even be part of tigron.
+package utils



Test patch:
diff --git a/mod/tigron/test/case.go b/mod/tigron/test/case.go
index 99f8101c..5b7ac14b 100644
--- a/mod/tigron/test/case.go
+++ b/mod/tigron/test/case.go
@@ -23,20 +23,23 @@ import (
 	"gotest.tools/v3/assert"
 )
 
-// Case describes an entire test-case, including data, setup and cleanup routines, command and expectations.
+// Case describes an entire test-case, including data, setup and cleanup routines, command and
+// expectations.
 type Case struct {
-	// Description contains a human-readable short desc, used as a seed for the identifier and as a title for the test
+	// Description contains a human-readable short desc, used as a seed for the identifier and as a
+	// title for the test.
 	Description string
-	// NoParallel disables parallel execution if set to true
+	// NoParallel disables parallel execution if set to true.
 	// This obviously implies that all tests run in parallel, by default. This is a design choice.
 	NoParallel bool
-	// Env contains a map of environment variables to use as a base for all commands run in Setup, Command and Cleanup
-	// Note that the environment is inherited by subtests
+	// Env contains a map of environment variables to use as a base for all commands run in Setup,
+	// Command and Cleanup.
+	// Note that the environment is inherited by subtests.
 	Env map[string]string
-	// Data contains test specific data, accessible to all operations, also inherited by subtests
+	// Data contains test specific data, accessible to all operations, also inherited by subtests.
 	Data Data
 	// Config contains specific information meaningful to the binary being tested.
-	// It is also inherited by subtests
+	// It is also inherited by subtests.
 	Config Config
 
 	// Requirement
@@ -73,7 +76,11 @@ func (test *Case) Run(t *testing.T) {
 
 		// Attach testing.T
 		test.t = subT
-		assert.Assert(test.t, test.Description != "" || test.parent == nil, "A test description cannot be empty")
+		assert.Assert(
+			test.t,
+			test.Description != "" || test.parent == nil,
+			"A test description cannot be empty",
+		)
 		assert.Assert(test.t, test.Command == nil || test.Expected != nil,
 			"Expectations for a test command cannot be nil. You may want to use Setup instead.")
 
diff --git a/mod/tigron/test/command.go b/mod/tigron/test/command.go
index cca07712..8c5daba3 100644
--- a/mod/tigron/test/command.go
+++ b/mod/tigron/test/command.go
@@ -14,6 +14,7 @@
    limitations under the License.
 */
 
+//nolint:revive
 package test
 
 import (
@@ -34,7 +35,8 @@ import (
 	"github.com/containerd/nerdctl/mod/tigron/test/internal/pty"
 )
 
-// CustomizableCommand is an interface meant for people who want to heavily customize the base command
+// CustomizableCommand is an interface meant for people who want to heavily customize the base
+// command
 // of their test case.
 type CustomizableCommand interface {
 	TestableCommand
@@ -52,8 +54,9 @@ type CustomizableCommand interface {
 	// WithConfig allows passing custom config properties from the test to the base command
 	withConfig(config Config)
 	withT(t *testing.T)
-	// Clear does a clone, but will clear binary and arguments, but retain the env, or any other custom properties
-	// Gotcha: if GenericCommand is embedded with a custom Run and an overridden clear to return the embedding type
+	// Clear does a clone, but will clear binary and arguments, but retain the env, or any other
+	// custom properties Gotcha: if GenericCommand is embedded with a custom Run and an overridden
+	// clear to return the embedding type
 	// the result will be the embedding command, no longer the GenericCommand
 	clear() TestableCommand
 
@@ -127,12 +130,13 @@ func (gc *GenericCommand) Run(expect *Expected) {
 		env    []string
 		tty    *os.File
 		psty   *os.File
+		stdout string
 	)
 
 	output := &bytes.Buffer{}
-	stdout := ""
 	copyGroup := &errgroup.Group{}
 
+	//nolint:nestif
 	if !gc.async {
 		iCmdCmd := gc.boot()
 
@@ -158,6 +162,7 @@ func (gc *GenericCommand) Run(expect *Expected) {
 					gc.t.Log("start command failed")
 					gc.t.Log(gc.result.ExitCode)
 					gc.t.Log(gc.result.Error)
+
 					return gc.result.Error
 				}
 
@@ -166,6 +171,7 @@ func (gc *GenericCommand) Run(expect *Expected) {
 					if err != nil {
 						gc.t.Log("writing to the pty failed")
 						gc.t.Log(err)
+
 						return err
 					}
 				}
@@ -205,7 +211,8 @@ func (gc *GenericCommand) Run(expect *Expected) {
 		// ExitCode goes first
 		switch expect.ExitCode {
 		case internal.ExitCodeNoCheck:
-			// ExitCodeNoCheck means we do not care at all about exit code. It can be a failure, a success, or a timeout.
+			// ExitCodeNoCheck means we do not care at all about exit code. It can be a failure, a
+			// success, or a timeout.
 		case internal.ExitCodeGenericFail:
 			// ExitCodeGenericFail means we expect an error (excluding timeout).
 			assert.Assert(gc.t, result.ExitCode != 0,
@@ -367,7 +374,8 @@ func (gc *GenericCommand) boot() icmd.Cmd {
 		}
 	}
 
-	// Ensure the subprocess gets executed in a temporary directory unless explicitly instructed otherwise
+	// Ensure the subprocess gets executed in a temporary directory unless explicitly instructed
+	// otherwise
 	iCmdCmd.Dir = gc.workingDir
 
 	if gc.stdin != nil {
diff --git a/mod/tigron/test/config.go b/mod/tigron/test/config.go
index 88001fec..366b3651 100644
--- a/mod/tigron/test/config.go
+++ b/mod/tigron/test/config.go
@@ -29,7 +29,7 @@ func WithConfig(key ConfigKey, value ConfigValue) Config {
 // Contains the implementation of the Config interface
 
 //nolint:ireturn
-func configureConfig(cfg Config, parent Config) Config {
+func configureConfig(cfg, parent Config) Config {
 	if cfg == nil {
 		cfg = &config{
 			config: make(map[ConfigKey]ConfigValue),
diff --git a/mod/tigron/test/data.go b/mod/tigron/test/data.go
index 28e020b8..59bb8218 100644
--- a/mod/tigron/test/data.go
+++ b/mod/tigron/test/data.go
@@ -31,7 +31,7 @@ const (
 // WithData returns a data object with a certain key value set.
 //
 //nolint:ireturn
-func WithData(key string, value string) Data {
+func WithData(key, value string) Data {
 	dat := &data{}
 	dat.Set(key, value)
 
@@ -41,7 +41,7 @@ func WithData(key string, value string) Data {
 // Contains the implementation of the Data interface
 
 //nolint:ireturn
-func configureData(t *testing.T, seedData Data, parent Data) Data {
+func configureData(t *testing.T, seedData, parent Data) Data {
 	t.Helper()
 
 	if seedData == nil {
@@ -78,7 +78,7 @@ func (dt *data) Get(key string) string {
 }
 
 //nolint:ireturn
-func (dt *data) Set(key string, value string) Data {
+func (dt *data) Set(key, value string) Data {
 	if dt.labels == nil {
 		dt.labels = map[string]string{}
 	}
diff --git a/mod/tigron/test/doc.go b/mod/tigron/test/doc.go
new file mode 100644
index 00000000..939a3937
--- /dev/null
+++ b/mod/tigron/test/doc.go
@@ -0,0 +1,18 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+// Package test is the main entrypoint for Tigron.
+package test
diff --git a/mod/tigron/test/funct.go b/mod/tigron/test/funct.go
index cc6f80c0..446c003a 100644
--- a/mod/tigron/test/funct.go
+++ b/mod/tigron/test/funct.go
@@ -21,11 +21,12 @@ import "testing"
 // An Evaluator is a function that decides whether a test should run or not.
 type Evaluator func(data Data, helpers Helpers) (bool, string)
 
-// A Butler is the function signature meant to be attached to a Setup or Cleanup routine for a Case or Requirement.
+// A Butler is the function signature meant to be attached to a Setup or Cleanup routine for a Case
+// or Requirement.
 type Butler func(data Data, helpers Helpers)
 
 // A Comparator is the function signature to implement for the Output property of an Expected.
-type Comparator func(stdout string, info string, t *testing.T)
+type Comparator func(stdout, info string, t *testing.T)
 
 // A Manager is the function signature meant to produce expectations for a command.
 type Manager func(data Data, helpers Helpers) *Expected
diff --git a/mod/tigron/test/helpers.go b/mod/tigron/test/helpers.go
index 3cdfdc2e..80725bd6 100644
--- a/mod/tigron/test/helpers.go
+++ b/mod/tigron/test/helpers.go
@@ -57,7 +57,7 @@ func (help *helpersInternal) Capture(args ...string) string {
 
 	help.Command(args...).Run(&Expected{
 		//nolint:thelper
-		Output: func(stdout string, _ string, _ *testing.T) {
+		Output: func(stdout, _ string, _ *testing.T) {
 			ret = stdout
 		},
 	})
diff --git a/mod/tigron/test/interfaces.go b/mod/tigron/test/interfaces.go
index 70f11007..14838039 100644
--- a/mod/tigron/test/interfaces.go
+++ b/mod/tigron/test/interfaces.go
@@ -24,15 +24,17 @@ import (
 )
 
 // Data is meant to hold information about a test:
-// - first, any random key value data that the test implementer wants to carry / modify - this is test data
-// - second, some commonly useful immutable test properties (a way to generate unique identifiers for that test,
+// - first, any random key value data that the test implementer wants to carry / modify - this is
+// test data - second, some commonly useful immutable test properties (a way to generate unique
+// identifiers for that test,
 // temporary directory, etc.)
-// Note that Data is inherited, from parent test to subtest (except for Identifier and TempDir of course).
+// Note that Data is inherited, from parent test to subtest (except for Identifier and TempDir of
+// course).
 type Data interface {
 	// Get returns the value of a certain key for custom data
 	Get(key string) string
 	// Set will save `value` for `key`
-	Set(key string, value string) Data
+	Set(key, value string) Data
 
 	// Identifier returns the test identifier that can be used to name resources
 	Identifier(suffix ...string) string
@@ -43,56 +45,57 @@ type Data interface {
 // Helpers provides a set of helpers to run commands with simple expectations,
 // available at all stages of a test (Setup, Cleanup, etc...)
 type Helpers interface {
-	// Ensure runs a command and verifies it is succeeding
+	// Ensure runs a command and verifies it is succeeding.
 	Ensure(args ...string)
-	// Anyhow runs a command and ignores its result
+	// Anyhow runs a command and ignores its result.
 	Anyhow(args ...string)
-	// Fail runs a command and verifies it failed
+	// Fail runs a command and verifies it failed.
 	Fail(args ...string)
-	// Capture runs a command, verifies it succeeded, and returns stdout
+	// Capture runs a command, verifies it succeeded, and returns stdout.
 	Capture(args ...string) string
-	// Err runs a command, and returns stderr regardless of its outcome
-	// This is mostly useful for debugging
+	// Err runs a command, and returns stderr regardless of its outcome.
+	// This is mostly useful for debugging.
 	Err(args ...string) string
 
-	// Command will return a populated command from the default internal command, with the provided arguments,
-	// ready to be Run or further configured
+	// Command will return a populated command from the default internal command, with the provided
+	// arguments, ready to be Run or further configured.
 	Command(args ...string) TestableCommand
-	// Custom will return a bare command, without configuration nor defaults (still has the Env)
+	// Custom will return a bare command, without configuration nor defaults (still has the Env).
 	Custom(binary string, args ...string) TestableCommand
 
-	// Read return the config value associated with a key
+	// Read return the config value associated with a key.
 	Read(key ConfigKey) ConfigValue
-	// Write saves a value in the config
+	// Write saves a value in the config.
 	Write(key ConfigKey, value ConfigValue)
 
-	// T returns the current testing object
+	// T returns the current testing object.
 	T() *testing.T
 }
 
-// The TestableCommand interface represents a low-level command to execute, typically to be compared with an Expected
-// A TestableCommand can be used as a Case Command obviously, but also as part of a Setup or Cleanup routine,
-// and as the basis of any type of helper.
+// The TestableCommand interface represents a low-level command to execute, typically to be compared
+// with an Expected. A TestableCommand can be used as a Case Command obviously, but also as part of
+// a Setup or Cleanup routine, and as the basis of any type of helper.
 // For more powerful use-cases outside of test cases, see below CustomizableCommand.
 type TestableCommand interface { //nolint:interfacebloat
-	// WithBinary specifies what binary to execute
+	// WithBinary specifies what binary to execute.
 	WithBinary(binary string)
-	// WithArgs specifies the args to pass to the binary. Note that WithArgs can be used multiple times and is additive.
+	// WithArgs specifies the args to pass to the binary. Note that WithArgs can be used multiple
+	// times and is additive.
 	WithArgs(args ...string)
-	// WithWrapper allows wrapping a command with another command (for example: `time`)
+	// WithWrapper allows wrapping a command with another command (for example: `time`).
 	WithWrapper(binary string, args ...string)
 	WithPseudoTTY(writers ...func(*os.File) error)
-	// WithStdin allows passing a reader to be used for stdin for the command
+	// WithStdin allows passing a reader to be used for stdin for the command.
 	WithStdin(r io.Reader)
-	// WithCwd allows specifying the working directory for the command
+	// WithCwd allows specifying the working directory for the command.
 	WithCwd(path string)
-	// Clone returns a copy of the command
+	// Clone returns a copy of the command.
 	Clone() TestableCommand
 
 	// Run does execute the command, and compare the output with the provided expectation.
 	// Passing nil for `Expected` will just run the command regardless of outcome.
-	// An empty `&Expected{}` is (of course) equivalent to &Expected{Exit: 0}, meaning the command is
-	// verified to be successful
+	// An empty `&Expected{}` is (of course) equivalent to &Expected{Exit: 0}, meaning the command
+	// is verified to be successful
 	Run(expect *Expected)
 	// Background allows starting a command in the background
 	Background(timeout time.Duration)
@@ -102,7 +105,8 @@ type TestableCommand interface { //nolint:interfacebloat
 	Stderr() string
 }
 
-// Config is meant to hold information relevant to the binary (eg: flags defining certain behaviors, etc.)
+// Config is meant to hold information relevant to the binary (eg: flags defining certain behaviors,
+// etc.)
 type Config interface {
 	Write(key ConfigKey, value ConfigValue) Config
 	Read(key ConfigKey) ConfigValue
diff --git a/mod/tigron/test/internal/consts.go b/mod/tigron/test/internal/consts.go
index dd058dcf..2fcb2680 100644
--- a/mod/tigron/test/internal/consts.go
+++ b/mod/tigron/test/internal/consts.go
@@ -14,6 +14,10 @@
    limitations under the License.
 */
 
+// Package internal provides an assert library, pty, a command wrapper, and a leak detection library
+// for internal use in Tigron.
+// The objective for these is not to become generic use-cases libraries, but instead to deliver what
+// Tigron needs in the simplest possible form.
 package internal
 
 // This is duplicated form `expect` to avoid circular imports.
diff --git a/mod/tigron/test/internal/pty/pty.go b/mod/tigron/test/internal/pty/pty.go
index 9aa32f3e..cd564a27 100644
--- a/mod/tigron/test/internal/pty/pty.go
+++ b/mod/tigron/test/internal/pty/pty.go
@@ -14,10 +14,10 @@
    limitations under the License.
 */
 
-// Package pty.
-// Note that creack is MIT licensed, making it better to depend on it rather than using derived code here.
-// Underlying creack implementation is OK though they have more (unnecessary to us) features and do not follow the
-// same coding standards.
+// Package pty provides a simple to manipulate pty Open method.
+// Note that creack is MIT licensed, making it better to depend on it rather than using derived code
+// here. Underlying implementation is OK though they have more (unnecessary to us) features and do
+// not follow the same coding standards.
 package pty
 
 import (
@@ -28,10 +28,13 @@ import (
 )
 
 var (
-	ErrFailure             = errors.New("pty failure")
+	// ErrFailure is wrapping system pty creation failure returned by Open().
+	ErrFailure = errors.New("pty failure")
+	// ErrUnsupportedPlatform is returned by Open() on unsupported platforms.
 	ErrUnsupportedPlatform = errors.New("pty not supported on this platform")
 )
 
+// Open will allocate and return a new pty.
 func Open() (*os.File, *os.File, error) {
 	pty, tty, err := creack.Open()
 	if err != nil {
diff --git a/mod/tigron/test/test.go b/mod/tigron/test/test.go
index 00cf7b70..274a783b 100644
--- a/mod/tigron/test/test.go
+++ b/mod/tigron/test/test.go
@@ -20,6 +20,7 @@ import (
 	"testing"
 )
 
+// Testable TODO.
 type Testable interface {
 	CustomCommand(testCase *Case, t *testing.T) CustomizableCommand
 	AmbientRequirements(testCase *Case, t *testing.T)
@@ -30,6 +31,7 @@ type Testable interface {
 //nolint:gochecknoglobals
 var registeredTestable Testable
 
+// Customize TODO.
 func Customize(testable Testable) {
 	registeredTestable = testable
 }
diff --git a/mod/tigron/test/types.go b/mod/tigron/test/types.go
index 7544d808..0b8130bc 100644
--- a/mod/tigron/test/types.go
+++ b/mod/tigron/test/types.go
@@ -17,29 +17,33 @@
 package test
 
 type (
-	ConfigKey   string
+	// ConfigKey FIXME consider getting rid of this?
+	ConfigKey string
+	// ConfigValue FIXME consider getting rid of this?
 	ConfigValue string
 )
 
-// A Requirement offers a way to verify random conditions to decide if a test should be skipped or run.
+// A Requirement offers a way to verify random conditions to decide if a test should be skipped or
+// run.
 // It can also (optionally) provide custom Setup and Cleanup routines.
 type Requirement struct {
-	// Check is expected to verify if the requirement is fulfilled, and return a boolean and an explanatory message
+	// Check is expected to verify if the requirement is fulfilled, and return a boolean and an
+	// explanatory message.
 	Check Evaluator
-	// Setup, if provided, will be run before any test-specific Setup routine, in the order that requirements
-	// have been declared
+	// Setup, if provided, will be run before any test-specific Setup routine, in the order that
+	// requirements have been declared.
 	Setup Butler
-	// Cleanup, if provided, will be run after any test-specific Cleanup routine, in the reverse order that
-	// requirements have been declared
+	// Cleanup, if provided, will be run after any test-specific Cleanup routine, in the reverse
+	// order that requirements have been declared.
 	Cleanup Butler
 }
 
 // Expected expresses the expected output of a command.
 type Expected struct {
-	// ExitCode
+	// ExitCode.
 	ExitCode int
-	// Errors contains any error that (once serialized) should be seen in stderr
+	// Errors contains any error that (once serialized) should be seen in stderr.
 	Errors []error
-	// Output function to match against stdout
+	// Output function to match against stdout.
 	Output Comparator
 }