A lightweight test helper package
0

Configure Feed

Select the types of activity you want to include in your feed.

Refresh golangci-lint config (#56)

authored by

Tom Fleet and committed by
GitHub
(Jan 19, 2025, 8:16 PM UTC) 02193c53 f30a903b

+189 -108
+102 -90
.golangci.yml
··· 1 1 linters: 2 - disable-all: true 3 - enable: 4 - - bodyclose 5 - - containedctx 6 - - durationcheck 7 - - errcheck 8 - - errchkjson 9 - - errname 10 - - exhaustive 11 - - goconst 12 - - gocritic 13 - - gocyclo 14 - - godot 15 - - gofumpt 16 - - goimports 17 - - gosimple 18 - - govet 19 - - ineffassign 20 - - misspell 21 - - mnd 22 - - nilerr 23 - - nilnil 24 - - nolintlint 25 - - nakedret 26 - - predeclared 27 - - reassign 28 - - revive 29 - - staticcheck 30 - - tagliatelle 31 - - tenv 32 - - testpackage 33 - - thelper 34 - - unconvert 35 - - unparam 36 - - unused 37 - - whitespace 2 + enable-all: true 3 + disable: 4 + - decorder # Don't care about this 5 + - dupl # Basically every table driven test ever triggers this 6 + - dupword # Messes with test cases more often than not 7 + - err113 # Out of date 8 + - exhaustruct # No 9 + - exportloopref # Go 1.22+ solves this 10 + - forbidigo # Nothing to forbid 11 + - funlen # Bad metric for complexity 12 + - gci # gofmt/goimports is fine 13 + - ginkgolinter # I don't use whatever this is 14 + - gochecknoglobals # Globals are fine sometimes, use common sense 15 + - gocyclo # cyclop does this instead 16 + - godox # "todo" and "fixme" comments are allowed 17 + - goheader # No need 18 + - gosmopolitan # No need 19 + - grouper # Imports take care of themselves, rest is common sense 20 + - ireturn # This is just not necessary or practical in a real codebase 21 + - lll # Auto formatters do this and what they can't do I don't care about 22 + - maintidx # This is just the inverse of complexity... which is cyclop 23 + - nestif # cyclop does this 24 + - nonamedreturns # Named returns are often helpful, it's naked returns that are the issue 25 + - paralleltest # I've never had Go tests take longer than a few seconds, it's fine 26 + - varnamelen # Lots of false positives of things that are fine 27 + - wrapcheck # Not every error must be wrapped 38 28 39 29 issues: 40 30 exclude-rules: 41 - - path: test_test.go 31 + - path: _test\.go 42 32 linters: 43 - - thelper # The entire package is effectively a t.Helper 44 - - goconst # Lots of repetition in here 33 + - prealloc # These kinds of optimisations will make no difference to test code 34 + - thelper # This entire package is a test helper 35 + - goconst # Lots of repeated test input 36 + 37 + exclude-files: 38 + - internal/diff/diff.go # Code taken from go/internal/diff 45 39 46 40 linters-settings: 41 + cyclop: 42 + max-complexity: 20 43 + 44 + depguard: 45 + rules: 46 + main: 47 + deny: 48 + - pkg: io/ioutil 49 + desc: io/ioutil is deprecated, use io instead 50 + 51 + - pkg: "math/rand$" 52 + desc: use math/rand/v2 instead 53 + 47 54 errcheck: 48 55 check-type-assertions: true 49 56 check-blank: true ··· 54 61 - map 55 62 default-signifies-exhaustive: true 56 63 57 - gocyclo: 58 - min-complexity: 20 59 - 60 64 staticcheck: 61 - checks: ["all"] 65 + checks: 66 + - all 62 67 63 68 gosimple: 64 - checks: ["all"] 69 + checks: 70 + - all 65 71 66 72 govet: 67 73 enable-all: true 68 74 75 + gofumpt: 76 + extra-rules: true 77 + 78 + nakedret: 79 + max-func-lines: 0 # Disallow any naked returns 80 + 81 + nolintlint: 82 + allow-unused: false 83 + require-explanation: true 84 + require-specific: true 85 + 86 + usetesting: 87 + context-background: true 88 + context-todo: true 89 + os-chdir: true 90 + os-mkdir-temp: true 91 + os-setenv: true 92 + os-create-temp: true 93 + os-temp-dir: true 94 + 69 95 revive: 70 96 max-open-files: 256 71 97 ignore-generated-header: true 98 + enable-all-rules: true 72 99 rules: 100 + - name: add-constant 101 + disabled: true # goconst does this 102 + 73 103 - name: argument-limit 74 - disabled: false 75 - arguments: [5] 104 + arguments: 105 + - 5 76 106 77 - - name: atomic 78 - disabled: false 107 + - name: cognitive-complexity 108 + disabled: true # gocognit does this 79 109 80 - - name: blank-imports 81 - disabled: false 110 + - name: comment-spacings 111 + arguments: 112 + - "nolint:" 82 113 83 - - name: call-to-gc 84 - disabled: false 85 - 86 - - name: constant-logical-expr 87 - disabled: false 88 - 89 - - name: context-as-argument 90 - disabled: false 91 - 92 - - name: datarace 93 - disabled: false 94 - 95 - - name: deep-exit 96 - disabled: false 97 - 98 - - name: defer 99 - disabled: false 100 - 101 - - name: dot-imports 102 - disabled: false 103 - 104 - - name: early-return 105 - disabled: false 114 + - name: cyclomatic 115 + disabled: true # cyclop does this 106 116 107 117 - name: exported 108 118 arguments: 109 119 - checkPrivateReceivers 120 + - checkPublicInterface 110 121 111 - - name: modifies-value-receiver 112 - disabled: false 122 + - name: flag-parameter 123 + disabled: true # Lots of false positives in this package 113 124 114 - - name: package-comments 115 - disabled: false 125 + - name: function-length 126 + disabled: true # Bad proxy for complexity 116 127 117 - - name: range 118 - disabled: false 128 + - name: function-result-limit 129 + arguments: 130 + - 3 119 131 120 - - name: range-val-in-closure 121 - disabled: false 132 + - name: import-shadowing 133 + disabled: true # predeclared does this 122 134 123 - - name: range-val-address 124 - disabled: false 135 + - name: line-length-limit 136 + disabled: true # gofmt/golines handles this well enough 125 137 126 - - name: time-equal 127 - disabled: false 138 + - name: redefines-builtin-id 139 + disabled: true # predeclared does this 128 140 129 - - name: use-any 130 - disabled: false 131 - 132 - - name: waitgroup-by-value 133 - disabled: false 141 + - name: unhandled-error 142 + arguments: 143 + - fmt\.(Fp|P)rint(ln|f)? 144 + - strings.Builder.Write(String|Byte)? 145 + - bytes.Buffer.Write(String|Byte)?
+2
.typos.toml
··· 1 + [default.extend-words] 2 + decorder = "decorder" # Name of a Go linter
-4
Taskfile.yml
··· 58 58 - sh: command -v golangci-lint 59 59 msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation 60 60 61 - - sh: command -v betteralign 62 - msg: requires betteralign, run `go install github.com/dkorunic/betteralign/cmd/betteralign@latest` 63 - 64 61 - sh: command -v typos 65 62 msg: requires typos-cli, run `brew install typos-cli` 66 63 cmds: 67 - - betteralign -test_files -apply ./... 68 64 - golangci-lint run --fix 69 65 - typos 70 66
+11 -2
config.go
··· 62 62 type Option interface { 63 63 // Apply the option to the test config, returning an error if the option 64 64 // cannot be applied for whatever reason. 65 - apply(*config) error 65 + apply(cfg *config) error 66 66 } 67 67 68 68 // option is a function adapter implementing the Option interface, analogous 69 69 // to how http.HandlerFunc implements the Handler interface. 70 - type option func(*config) error 70 + type option func(cfg *config) error 71 71 72 72 // apply applies the option, implementing the Option interface for the option 73 73 // function adapter. ··· 87 87 if math.IsInf(threshold, 0) { 88 88 return errors.New("cannot set floating point equality threshold to ±infinity") 89 89 } 90 + 90 91 cfg.floatEqualityThreshold = threshold 92 + 91 93 return nil 92 94 } 95 + 93 96 return option(f) 94 97 } 95 98 ··· 109 112 if title == "" { 110 113 return errors.New("cannot set title to an empty string") 111 114 } 115 + 112 116 cfg.title = strings.TrimSpace(title) 117 + 113 118 return nil 114 119 } 120 + 115 121 return option(f) 116 122 } 117 123 ··· 134 140 if format == "" { 135 141 return errors.New("cannot set context to an empty string") 136 142 } 143 + 137 144 context := fmt.Sprintf(format, args...) 138 145 cfg.context = strings.TrimSpace(context) 146 + 139 147 return nil 140 148 } 149 + 141 150 return option(f) 142 151 }
+35 -3
test.go
··· 31 31 // test.Equal(t, "apples", "oranges") // Fails 32 32 func Equal[T comparable](tb testing.TB, got, want T, options ...Option) { 33 33 tb.Helper() 34 + 34 35 cfg := defaultConfig() 35 36 cfg.title = "Not Equal" 36 37 37 38 for _, option := range options { 38 39 if err := option.apply(&cfg); err != nil { 39 40 tb.Fatalf("Equal: could not apply options: %v", err) 41 + 40 42 return 41 43 } 42 44 } ··· 57 59 // test.NotEqual(t, 42, 42) // Fails 58 60 func NotEqual[T comparable](tb testing.TB, got, want T, options ...Option) { 59 61 tb.Helper() 62 + 60 63 cfg := defaultConfig() 61 64 cfg.title = "Equal" 62 65 63 66 for _, option := range options { 64 67 if err := option.apply(&cfg); err != nil { 65 68 tb.Fatalf("NotEqual: could not apply options: %v", err) 69 + 66 70 return 67 71 } 68 72 } ··· 89 93 // test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Fails 90 94 func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { 91 95 tb.Helper() 96 + 92 97 cfg := defaultConfig() 93 98 cfg.title = "Not Equal" 94 99 95 100 for _, option := range options { 96 101 if err := option.apply(&cfg); err != nil { 97 102 tb.Fatalf("EqualFunc: could not apply options: %v", err) 103 + 98 104 return 99 105 } 100 106 } ··· 122 128 // test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes 123 129 func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { 124 130 tb.Helper() 131 + 125 132 cfg := defaultConfig() 126 133 cfg.title = "Equal" 127 134 128 135 for _, option := range options { 129 136 if err := option.apply(&cfg); err != nil { 130 137 tb.Fatalf("NotEqualFunc: could not apply options: %v", err) 138 + 131 139 return 132 140 } 133 141 } ··· 152 160 // test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different 153 161 func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) { 154 162 tb.Helper() 163 + 155 164 cfg := defaultConfig() 156 165 cfg.title = "Not NearlyEqual" 157 166 158 167 for _, option := range options { 159 168 if err := option.apply(&cfg); err != nil { 160 169 tb.Fatalf("NearlyEqual: could not apply options: %v", err) 170 + 161 171 return 162 172 } 163 173 } ··· 186 196 // test.Ok(t, err) 187 197 func Ok(tb testing.TB, err error, options ...Option) { 188 198 tb.Helper() 199 + 189 200 cfg := defaultConfig() 190 201 cfg.title = "Not Ok" 191 202 192 203 for _, option := range options { 193 204 if optionErr := option.apply(&cfg); optionErr != nil { 194 205 tb.Fatalf("Ok: could not apply options: %v", optionErr) 206 + 195 207 return 196 208 } 197 209 } ··· 212 224 // test.Err(t, err) 213 225 func Err(tb testing.TB, err error, options ...Option) { 214 226 tb.Helper() 227 + 215 228 cfg := defaultConfig() 216 229 cfg.title = "Not Err" 217 230 218 231 for _, option := range options { 219 232 if optionErr := option.apply(&cfg); optionErr != nil { 220 233 tb.Fatalf("Err: could not apply options: %v", optionErr) 234 + 221 235 return 222 236 } 223 237 } ··· 244 258 // test.WantErr(t, nil, false) // Passes, didn't want an error and didn't get one 245 259 func WantErr(tb testing.TB, err error, want bool, options ...Option) { 246 260 tb.Helper() 261 + 247 262 cfg := defaultConfig() 248 263 cfg.title = "WantErr" 249 264 250 265 for _, option := range options { 251 266 if optionErr := option.apply(&cfg); optionErr != nil { 252 267 tb.Fatalf("WantErr: could not apply options: %v", optionErr) 268 + 253 269 return 254 270 } 255 271 } ··· 259 275 reason string 260 276 wanted error 261 277 ) 278 + 262 279 if want { 263 280 reason = fmt.Sprintf("Wanted an error but got %v", err) 264 281 wanted = errors.New("error") ··· 266 283 reason = fmt.Sprintf("Got an unexpected error: %v", err) 267 284 wanted = nil 268 285 } 286 + 269 287 cfg.reason = reason 270 288 fail := failure[error]{ 271 289 got: err, ··· 282 300 // test.True(t, false) // Fails 283 301 func True(tb testing.TB, got bool, options ...Option) { 284 302 tb.Helper() 303 + 285 304 cfg := defaultConfig() 286 305 cfg.title = "Not True" 287 306 288 307 for _, option := range options { 289 308 if err := option.apply(&cfg); err != nil { 290 309 tb.Fatalf("True: could not apply options: %v", err) 310 + 291 311 return 292 312 } 293 313 } ··· 308 328 // test.False(t, true) // Fails 309 329 func False(tb testing.TB, got bool, options ...Option) { 310 330 tb.Helper() 331 + 311 332 cfg := defaultConfig() 312 333 cfg.title = "Not False" 313 334 314 335 for _, option := range options { 315 336 if err := option.apply(&cfg); err != nil { 316 337 tb.Fatalf("False: could not apply options: %v", err) 338 + 317 339 return 318 340 } 319 341 } ··· 395 417 stderrCapture := make(chan string) 396 418 397 419 var wg sync.WaitGroup 398 - wg.Add(2) //nolint: mnd 420 + 421 + wg.Add(2) //nolint: mnd // 2 because stdout and stderr 399 422 400 423 // Copy in goroutines to avoid blocking 401 424 go func(wg *sync.WaitGroup) { ··· 403 426 close(stdoutCapture) 404 427 wg.Done() 405 428 }() 429 + 406 430 buf := &bytes.Buffer{} 407 431 if _, err := io.Copy(buf, stdoutReader); err != nil { 408 432 tb.Fatalf("CaptureOutput: failed to copy from stdout reader: %v", err) ··· 415 439 close(stderrCapture) 416 440 wg.Done() 417 441 }() 442 + 418 443 buf := &bytes.Buffer{} 419 444 if _, err := io.Copy(buf, stderrReader); err != nil { 420 445 tb.Fatalf("CaptureOutput: failed to copy from stderr reader: %v", err) ··· 428 453 } 429 454 430 455 // Close the writers 431 - stdoutWriter.Close() 432 - stderrWriter.Close() 456 + stdoutCloseErr := stdoutWriter.Close() 457 + if stdoutCloseErr != nil { 458 + tb.Fatalf("CaptureOutput: could not close stdout pipe: %v", stdoutCloseErr) 459 + } 460 + 461 + stderrCloseErr := stderrWriter.Close() 462 + if stderrCloseErr != nil { 463 + tb.Fatalf("CaptueOutput: could not close stderr pipe: %v", stderrCloseErr) 464 + } 433 465 434 466 capturedStdout := <-stdoutCapture 435 467 capturedStderr := <-stderrCapture
+4 -4
test_test.go
··· 24 24 failed bool 25 25 } 26 26 27 - func (t *TB) Helper() {} 27 + func (t *TB) Helper() {} //nolint: revive // We don't use t because it does nothing 28 28 29 29 func (t *TB) Fatal(args ...any) { 30 30 t.failed = true ··· 122 122 { 123 123 name: "EqualFunc/fail", 124 124 fn: func(tb testing.TB) { 125 - cmp := func(a, b []string) bool { return false } // Cheating 125 + cmp := func(_, _ []string) bool { return false } // Cheating 126 126 test.EqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) 127 127 }, 128 128 wantFail: true, ··· 170 170 { 171 171 name: "NotEqualFunc/fail", 172 172 fn: func(tb testing.TB) { 173 - cmp := func(a, b []string) bool { return true } // Cheating 173 + cmp := func(_, _ []string) bool { return true } // Cheating 174 174 test.NotEqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) 175 175 }, 176 176 wantFail: true, ··· 267 267 { 268 268 name: "Err/pass", 269 269 fn: func(tb testing.TB) { 270 - test.Err(tb, errors.New("bang!")) 270 + test.Err(tb, errors.New("bang")) 271 271 }, 272 272 wantFail: false, 273 273 },
+30 -5
internal/diff/diff.go
··· 48 48 // Second, the name is frequently interpreted as meaning that you have 49 49 // to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, 50 50 // when in fact the algorithm is faster than the standard one. 51 - func Diff( //nolint: gocyclo 51 + func Diff( 52 52 oldName string, 53 53 old []byte, 54 54 newName string, 55 - new []byte, //nolint: predeclared 55 + new []byte, 56 56 ) []byte { 57 57 if bytes.Equal(old, new) { 58 58 return nil 59 59 } 60 + 60 61 x := lines(old) 61 62 y := lines(new) 62 63 63 64 // Print diff header. 64 65 var out bytes.Buffer 66 + 65 67 fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) 66 68 fmt.Fprintf(&out, "--- %s\n", oldName) 67 69 fmt.Fprintf(&out, "+++ %s\n", newName) ··· 78 80 count pair // number of lines from each side in current chunk 79 81 ctext []string // lines for current chunk 80 82 ) 83 + 81 84 for _, m := range tgs(x, y) { 82 85 if m.x < done.x { 83 86 // Already handled scanning forward from earlier match. ··· 93 96 start.x-- 94 97 start.y-- 95 98 } 99 + 96 100 end := m 97 101 for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { 98 102 end.x++ ··· 105 109 ctext = append(ctext, "- "+s) 106 110 count.x++ 107 111 } 112 + 108 113 for _, s := range y[done.y:start.y] { 109 114 ctext = append(ctext, "+ "+s) 110 115 count.y++ ··· 120 125 count.x++ 121 126 count.y++ 122 127 } 128 + 123 129 done = end 130 + 124 131 continue 125 132 } 126 133 ··· 130 137 if n > C { 131 138 n = C 132 139 } 140 + 133 141 for _, s := range x[start.x : start.x+n] { 134 142 ctext = append(ctext, " "+s) 135 143 count.x++ 136 144 count.y++ 137 145 } 146 + 138 147 done = pair{start.x + n, start.y + n} 139 148 140 149 // Format and emit chunk. ··· 143 152 if count.x > 0 { 144 153 chunk.x++ 145 154 } 155 + 146 156 if count.y > 0 { 147 157 chunk.y++ 148 158 } 159 + 149 160 fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) 161 + 150 162 for _, s := range ctext { 151 163 out.WriteString(s) 152 164 } 165 + 153 166 count.x = 0 154 167 count.y = 0 155 168 ctext = ctext[:0] ··· 167 180 count.x++ 168 181 count.y++ 169 182 } 183 + 170 184 done = end 171 185 } 172 186 ··· 185 199 // using the same text as BSD/GNU diff (including the leading backslash). 186 200 l[len(l)-1] += "\n\\ No newline at end of file\n" 187 201 } 202 + 188 203 return l 189 204 } 190 205 ··· 207 222 m[s] = c - 1 208 223 } 209 224 } 225 + 210 226 for _, s := range y { 211 227 if c := m[s]; c > -8 { 212 - m[s] = c - 4 //nolint: mnd 228 + m[s] = c - 4 213 229 } 214 230 } 215 231 ··· 220 236 // yi[i] = increasing indexes of unique strings in y. 221 237 // inv[i] = index j such that x[xi[i]] = y[yi[j]]. 222 238 var xi, yi, inv []int 239 + 223 240 for i, s := range y { 224 241 if m[s] == -1+-4 { 225 242 m[s] = len(yi) 226 243 yi = append(yi, i) 227 244 } 228 245 } 246 + 229 247 for i, s := range x { 230 248 if j, ok := m[s]; ok && j >= 0 { 231 249 xi = append(xi, i) ··· 241 259 n := len(xi) 242 260 T := make([]int, n) 243 261 L := make([]int, n) 262 + 244 263 for i := range T { 245 264 T[i] = n + 1 246 265 } 247 - for i := 0; i < n; i++ { 266 + 267 + for i := range n { 248 268 k := sort.Search(n, func(k int) bool { 249 269 return T[k] >= J[i] 250 270 }) 251 271 T[k] = J[i] 252 272 L[i] = k + 1 253 273 } 274 + 254 275 k := 0 255 276 for _, v := range L { 256 277 if k < v { 257 278 k = v 258 279 } 259 280 } 260 - seq := make([]pair, 2+k) //nolint:mnd 281 + 282 + seq := make([]pair, 2+k) 261 283 seq[1+k] = pair{len(x), len(y)} // sentinel at end 284 + 262 285 lastj := n 263 286 for i := n - 1; i >= 0; i-- { 264 287 if L[i] == k && J[i] < lastj { ··· 266 289 k-- 267 290 } 268 291 } 292 + 269 293 seq[0] = pair{0, 0} // sentinel at start 294 + 270 295 return seq 271 296 }
+5
internal/diff/diff_test.go
··· 17 17 func clean(text []byte) []byte { 18 18 text = bytes.ReplaceAll(text, []byte("$\n"), []byte("\n")) 19 19 text = bytes.TrimSuffix(text, []byte("^D\n")) 20 + 20 21 return text 21 22 } 22 23 ··· 25 26 if err != nil { 26 27 t.Fatalf("could not glob txtar files: %v", err) 27 28 } 29 + 28 30 if len(files) == 0 { 29 31 t.Fatalf("no testdata") 30 32 } ··· 37 39 } 38 40 // Stupid windows 39 41 contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n")) 42 + 40 43 archive := txtar.Parse(contents) 41 44 if len(archive.Files) != 3 || archive.Files[2].Name != "diff" { 42 45 t.Fatalf("%s: want three files, third named \"diff\", got: %v", file, archive.Files) 43 46 } 47 + 44 48 diffs := diff.Diff( 45 49 archive.Files[0].Name, 46 50 clean(archive.Files[0].Data), ··· 48 52 clean(archive.Files[1].Data), 49 53 ) 50 54 want := clean(archive.Files[2].Data) 55 + 51 56 if !bytes.Equal(diffs, want) { 52 57 t.Fatalf("%s: have:\n%s\nwant:\n%s\n%s", file, 53 58 diffs, want, diff.Diff("have", diffs, "want", want))