Skip to content

Commit e181697

Browse files
authored
housekeeping: tighten linting, testing, and verification standards (#47)
* update file permissions to 0o644 in test case for consistency * refactor: simplify and standardize mutex usage, improve error messages, and enhance code consistency * refactor: improve code comments, rename function return values for clarity, and standardize formatting * add comment explaining purpose of `removeIPs` function * add `test-short` target to Makefile for running quick tests without race detection * increase coverage target to 80% and disable informational comments in codecov config * document `test-short` target, verification process, and acceptance criteria in CLAUDE.md * update CI workflows: enable caching, enforce 80% coverage, and standardize job configuration * remove unnecessary package comment in `add.go` * update `.golangci.yml`: enable additional linters, configure settings, and improve exclusions * update `.golangci.yml`: adjust complexity thresholds, add `argument-limit` linter, and refine exclusions * refactor `fuzz_test.go`: extract `validateParsedHostLine` helper, replace inline validation logic, and reuse `testBaseHosts` constant for clarity and consistency * update CLAUDE.md: document enhanced verification steps, updated complexity thresholds, and new testing rules * update GNUmakefile: add `coverage-check` target, enforce 80% coverage, refine `verify` and `verify-full` definitions * refactor `property_test.go`: replace manual loops with `slices.Contains`, extract `validateParsedLines` helper, and reuse `testHostsLocalhost` constant for clarity and consistency * refactor `root_test.go`: extract `setupTestHosts` and `captureOutput` helpers, reuse constants for clarity, and replace repetitive setup logic for improved consistency * refactor `txeh.go`: replace `"windows"` string literals with `osWindows` constant for clarity and consistency * refactor `txeh_test.go`: replace manual loops with `slices.Contains`, extract `createTempHostsFile` helper, reuse constants for clarity, and improve test consistency
1 parent 7e2837d commit e181697

File tree

13 files changed

+582
-493
lines changed

13 files changed

+582
-493
lines changed

.github/workflows/ci.yml

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,31 @@ on:
66
pull_request:
77
branches: [main, master]
88

9-
permissions:
10-
contents: read
9+
permissions: read-all
1110

1211
jobs:
1312
lint:
13+
name: Lint
1414
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
1517
steps:
1618
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1719
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
1820
with:
1921
go-version-file: go.mod
22+
cache: true
2023
- uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
2124
with:
22-
version: latest
25+
version: v2.8.0
2326
args: --timeout=5m
27+
only-new-issues: true
2428

2529
test:
30+
name: Test
2631
runs-on: ${{ matrix.os }}
32+
permissions:
33+
contents: read
2734
strategy:
2835
matrix:
2936
os: [ubuntu-latest, macos-latest]
@@ -32,10 +39,21 @@ jobs:
3239
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
3340
with:
3441
go-version-file: go.mod
42+
cache: true
3543

3644
- name: Run tests
3745
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
3846

47+
- name: Check coverage threshold
48+
if: matrix.os == 'ubuntu-latest'
49+
run: |
50+
COVERAGE=$(go tool cover -func=coverage.txt | grep total | awk '{print $3}' | sed 's/%//')
51+
echo "Total coverage: ${COVERAGE}%"
52+
if [ "$(echo "$COVERAGE < 80.0" | bc)" -eq 1 ]; then
53+
echo "Coverage ${COVERAGE}% is below 80% threshold"
54+
exit 1
55+
fi
56+
3957
- name: Upload coverage
4058
if: matrix.os == 'ubuntu-latest'
4159
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
@@ -44,12 +62,16 @@ jobs:
4462
token: ${{ secrets.CODECOV_TOKEN }}
4563

4664
build:
65+
name: Build
4766
runs-on: ubuntu-latest
67+
permissions:
68+
contents: read
4869
steps:
4970
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5071
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
5172
with:
5273
go-version-file: go.mod
74+
cache: true
5375

5476
- name: Build
5577
run: go build -v ./...
@@ -58,12 +80,16 @@ jobs:
5880
run: go mod verify
5981

6082
security:
83+
name: Security Scan
6184
runs-on: ubuntu-latest
85+
permissions:
86+
contents: read
6287
steps:
6388
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
6489
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
6590
with:
6691
go-version-file: go.mod
92+
cache: true
6793

6894
- name: Run gosec
6995
uses: securego/gosec@424fc4cd9c82ea0fd6bee9cd49c2db2c3cc0c93f # v2.22.11

.golangci.yml

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ linters:
2121
- durationcheck # Detect duration multiplication issues
2222
- makezero # Find slice append issues
2323
- wastedassign # Find wasted assignments
24+
- copyloopvar # Loop variable copying
2425

2526
# ===== Code Style =====
2627
- misspell # Spelling mistakes
2728
- unconvert # Unnecessary type conversions
2829
- whitespace # Whitespace issues
30+
- godot # Comment punctuation enforcement
31+
- revive # Comprehensive style/safety rules
2932

3033
# ===== Code Quality =====
3134
- gocritic # Comprehensive checks
@@ -38,13 +41,30 @@ linters:
3841
- unparam # Unused parameters
3942
- errname # Error naming conventions
4043
- errorlint # Error wrapping issues
44+
- goconst # Detect repeated string literals
45+
- thelper # Test helper validation
46+
- modernize # Go modernization suggestions
47+
48+
# ===== Security =====
49+
- gosec # Security scanning (OWASP)
50+
51+
# ===== Error Handling =====
52+
- wrapcheck # Ensure external package errors are wrapped
4153

4254
settings:
55+
errcheck:
56+
check-type-assertions: true
57+
4358
gocyclo:
44-
min-complexity: 15
59+
min-complexity: 10
4560

4661
gocognit:
47-
min-complexity: 20
62+
min-complexity: 15
63+
64+
govet:
65+
disable:
66+
- fieldalignment
67+
- shadow
4868

4969
misspell:
5070
locale: US
@@ -53,12 +73,19 @@ linters:
5373
max-func-lines: 30
5474

5575
nestif:
56-
min-complexity: 6
76+
min-complexity: 5
77+
78+
prealloc:
79+
simple: true
80+
range-loops: true
81+
for-loops: true
5782

5883
gocritic:
5984
enabled-tags:
6085
- diagnostic
6186
- performance
87+
- style
88+
- opinionated
6289
disabled-checks:
6390
- hugeParam
6491
- rangeValCopy
@@ -69,23 +96,94 @@ linters:
6996
asserts: true
7097
comparison: true
7198

99+
gosec:
100+
excludes:
101+
- G104
102+
config:
103+
G306: "0644"
104+
105+
goconst:
106+
min-len: 3
107+
min-occurrences: 3
108+
109+
wrapcheck:
110+
ignore-sigs:
111+
- ".Errorf("
112+
- "errors.New("
113+
- "errors.Unwrap("
114+
- "errors.Join("
115+
- ".Wrap("
116+
- ".Wrapf("
117+
- ".WithMessage("
118+
- ".WithMessagef("
119+
- ".WithStack("
120+
121+
revive:
122+
rules:
123+
# Style
124+
- name: blank-imports
125+
- name: context-as-argument
126+
- name: dot-imports
127+
- name: error-return
128+
- name: error-strings
129+
- name: error-naming
130+
- name: exported
131+
arguments:
132+
- "checkPrivateReceivers"
133+
- name: increment-decrement
134+
- name: var-naming
135+
- name: var-declaration
136+
- name: package-comments
137+
- name: range
138+
- name: receiver-naming
139+
- name: indent-error-flow
140+
- name: superfluous-else
141+
- name: modifies-parameter
142+
- name: unreachable-code
143+
- name: modifies-value-receiver
144+
- name: constant-logical-expr
145+
- name: bool-literal-in-expr
146+
- name: redefines-builtin-id
147+
- name: range-val-in-closure
148+
- name: range-val-address
149+
- name: waitgroup-by-value
150+
- name: atomic
151+
- name: call-to-gc
152+
- name: duplicated-imports
153+
- name: string-of-int
154+
- name: defer
155+
arguments:
156+
- ["call-chain", "loop", "method-call", "recover", "immediate-recover", "return"]
157+
- name: unconditional-recursion
158+
- name: identical-branches
159+
- name: unexported-naming
160+
- name: unnecessary-stmt
161+
- name: useless-break
162+
- name: context-keys-type
163+
- name: time-equal
164+
- name: time-naming
165+
- name: errorf
166+
- name: empty-block
167+
- name: get-return
168+
- name: early-return
169+
- name: unused-parameter
170+
disabled: true
171+
- name: confusing-naming
172+
- name: confusing-results
173+
- name: argument-limit
174+
arguments:
175+
- 5
176+
72177
exclusions:
178+
generated: lax
73179
warn-unused: true
74180
rules:
75-
# Relax rules for tests
76-
- path: _test\.go
77-
linters:
78-
- errcheck
79-
- gocritic
80-
- gocognit
81-
- gocyclo
82-
- unparam
83-
84181
# Allow longer functions in cmd/
85182
- path: cmd/
86183
linters:
87184
- gocyclo
88185
- gocognit
186+
- wrapcheck
89187

90188
# Generated files
91189
- path: generated
@@ -96,7 +194,9 @@ formatters:
96194
enable:
97195
- gofmt
98196
- goimports
99-
197+
- gofumpt
198+
exclusions:
199+
generated: lax
100200
settings:
101201
goimports:
102202
local-prefixes:

CLAUDE.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@ txeh is a Go library and CLI utility for managing /etc/hosts file entries. It pr
1818

1919
```bash
2020
# Full verification (run before every commit)
21-
make verify # fmt, lint, test-unit, security, go mod verify
21+
make verify # fmt, lint, test-unit, security, coverage-check, go mod verify
2222

23-
# Extended verification
24-
make verify-full # fmt, lint, test, security, go mod verify
23+
# Extended verification (adds dead-code analysis)
24+
make verify-full # fmt, lint, test, security, coverage-check, dead-code, go mod verify
2525

2626
# Format, lint, test individually
2727
make fmt # gofmt + goimports
28-
make lint # golangci-lint
28+
make lint # golangci-lint (37 linters, no test relaxations)
2929
make test-unit # go test -race ./...
30+
make test-short # Fast tests (no race detection)
3031
make coverage # Coverage report (HTML + summary)
32+
make coverage-check # Enforce 80% coverage threshold
3133
make security # gosec + govulncheck
34+
make mutate # Mutation testing (gremlins, 60% efficacy threshold)
3235
```
3336

3437
## Architecture
@@ -68,8 +71,9 @@ make security # gosec + govulncheck
6871
### Style
6972
- Follow [Google Go Style Guide](https://google.github.io/styleguide/go/)
7073
- All code must pass `golangci-lint` with project config
71-
- Maximum cyclomatic complexity: 15
72-
- Maximum cognitive complexity: 20
74+
- Maximum cyclomatic complexity: 10 per function
75+
- Maximum cognitive complexity: 15 per function
76+
- Maximum function arguments: 5
7377

7478
### Documentation
7579
- All exported functions, types, and packages require doc comments
@@ -93,10 +97,42 @@ func (h *Hosts) Save() error {
9397
```
9498

9599
### Testing
96-
- Test coverage minimum: 82%
100+
- Test coverage minimum: 80% (CI enforced)
97101
- Use table-driven tests for multiple cases
98102
- Use `t.Parallel()` for independent tests
99103
- Race detection required: `go test -race ./...`
104+
- Property-based tests with `rapid` for invariant verification
105+
106+
### Verification Stack
107+
108+
All changes go through a 4-level verification process (see [AI-Verified Development](https://imti.co/ai-verified-development/)):
109+
110+
1. **Static analysis** - `make lint` runs golangci-lint with 37 linters. Runs in seconds, catches type errors, complexity violations, security anti-patterns.
111+
2. **Unit tests** - `make test-unit` runs with `-race`. Coverage gate at 80%. `make coverage-check` enforces this locally.
112+
3. **Integration/E2E tests** - Tagged test suites (`-tags=integration`, `-tags=e2e`) for system-level validation.
113+
4. **Mutation testing** - `make mutate` runs gremlins with a 60% efficacy threshold. Validates that tests catch real bugs, not just exercise code.
114+
115+
Do not review AI code until every level passes. `make verify` runs levels 1-2 plus security. `make verify-full` adds dead-code analysis.
116+
117+
### Acceptance Criteria
118+
119+
Define specs as Given/When/Then assertions before writing code. Each "then" becomes a concrete test with a hardcoded expected value.
120+
121+
```
122+
Given a hosts file with "127.0.0.1 myhost"
123+
When RemoveHost("myhost") is called
124+
Then ListHostsByIP("127.0.0.1") returns an empty list
125+
```
126+
127+
Write the test first, confirm it fails, then implement.
128+
129+
### AI-Specific Rules
130+
131+
- **No tautological tests.** Tests must encode specific expected outputs as hardcoded values. Never reimplement function logic inside assertions.
132+
- **No hallucinated imports.** Verify every dependency exists and is actively maintained before adding it.
133+
- **Human review required.** A human must review and approve every line of code before commit.
134+
- **Non-obvious decisions require comments.** If a choice isn't self-evident, explain why in a comment or commit message.
135+
- **Dead code detection.** Run `make dead-code` to remove untested utilities. Unreachable code is a sign of tautological tests.
100136

101137
## Security Requirements
102138

@@ -148,10 +184,10 @@ make lint-fix # Run golangci-lint with auto-fix
148184
make fmt # Format code (gofmt + goimports)
149185

150186
# Before committing
151-
make verify # fmt, lint, test-unit, security, go mod verify
187+
make verify # fmt, lint, test-unit, security, coverage-check, go mod verify
152188

153189
# Extended verification
154-
make verify-full # fmt, lint, test, security, go mod verify
190+
make verify-full # fmt, lint, test, security, coverage-check, dead-code, go mod verify
155191

156192
# Testing
157193
make test-integration # Integration tests (tagged)

0 commit comments

Comments
 (0)