···6262type Option interface {
6363 // Apply the option to the test config, returning an error if the option
6464 // cannot be applied for whatever reason.
6565- apply(*config) error
6565+ apply(cfg *config) error
6666}
67676868// option is a function adapter implementing the Option interface, analogous
6969// to how http.HandlerFunc implements the Handler interface.
7070-type option func(*config) error
7070+type option func(cfg *config) error
71717272// apply applies the option, implementing the Option interface for the option
7373// function adapter.
···8787 if math.IsInf(threshold, 0) {
8888 return errors.New("cannot set floating point equality threshold to ±infinity")
8989 }
9090+9091 cfg.floatEqualityThreshold = threshold
9292+9193 return nil
9294 }
9595+9396 return option(f)
9497}
9598···109112 if title == "" {
110113 return errors.New("cannot set title to an empty string")
111114 }
115115+112116 cfg.title = strings.TrimSpace(title)
117117+113118 return nil
114119 }
120120+115121 return option(f)
116122}
117123···134140 if format == "" {
135141 return errors.New("cannot set context to an empty string")
136142 }
143143+137144 context := fmt.Sprintf(format, args...)
138145 cfg.context = strings.TrimSpace(context)
146146+139147 return nil
140148 }
149149+141150 return option(f)
142151}
+35-3
test.go
···3131// test.Equal(t, "apples", "oranges") // Fails
3232func Equal[T comparable](tb testing.TB, got, want T, options ...Option) {
3333 tb.Helper()
3434+3435 cfg := defaultConfig()
3536 cfg.title = "Not Equal"
36373738 for _, option := range options {
3839 if err := option.apply(&cfg); err != nil {
3940 tb.Fatalf("Equal: could not apply options: %v", err)
4141+4042 return
4143 }
4244 }
···5759// test.NotEqual(t, 42, 42) // Fails
5860func NotEqual[T comparable](tb testing.TB, got, want T, options ...Option) {
5961 tb.Helper()
6262+6063 cfg := defaultConfig()
6164 cfg.title = "Equal"
62656366 for _, option := range options {
6467 if err := option.apply(&cfg); err != nil {
6568 tb.Fatalf("NotEqual: could not apply options: %v", err)
6969+6670 return
6771 }
6872 }
···8993// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Fails
9094func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) {
9195 tb.Helper()
9696+9297 cfg := defaultConfig()
9398 cfg.title = "Not Equal"
949995100 for _, option := range options {
96101 if err := option.apply(&cfg); err != nil {
97102 tb.Fatalf("EqualFunc: could not apply options: %v", err)
103103+98104 return
99105 }
100106 }
···122128// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes
123129func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) {
124130 tb.Helper()
131131+125132 cfg := defaultConfig()
126133 cfg.title = "Equal"
127134128135 for _, option := range options {
129136 if err := option.apply(&cfg); err != nil {
130137 tb.Fatalf("NotEqualFunc: could not apply options: %v", err)
138138+131139 return
132140 }
133141 }
···152160// test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different
153161func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) {
154162 tb.Helper()
163163+155164 cfg := defaultConfig()
156165 cfg.title = "Not NearlyEqual"
157166158167 for _, option := range options {
159168 if err := option.apply(&cfg); err != nil {
160169 tb.Fatalf("NearlyEqual: could not apply options: %v", err)
170170+161171 return
162172 }
163173 }
···186196// test.Ok(t, err)
187197func Ok(tb testing.TB, err error, options ...Option) {
188198 tb.Helper()
199199+189200 cfg := defaultConfig()
190201 cfg.title = "Not Ok"
191202192203 for _, option := range options {
193204 if optionErr := option.apply(&cfg); optionErr != nil {
194205 tb.Fatalf("Ok: could not apply options: %v", optionErr)
206206+195207 return
196208 }
197209 }
···212224// test.Err(t, err)
213225func Err(tb testing.TB, err error, options ...Option) {
214226 tb.Helper()
227227+215228 cfg := defaultConfig()
216229 cfg.title = "Not Err"
217230218231 for _, option := range options {
219232 if optionErr := option.apply(&cfg); optionErr != nil {
220233 tb.Fatalf("Err: could not apply options: %v", optionErr)
234234+221235 return
222236 }
223237 }
···244258// test.WantErr(t, nil, false) // Passes, didn't want an error and didn't get one
245259func WantErr(tb testing.TB, err error, want bool, options ...Option) {
246260 tb.Helper()
261261+247262 cfg := defaultConfig()
248263 cfg.title = "WantErr"
249264250265 for _, option := range options {
251266 if optionErr := option.apply(&cfg); optionErr != nil {
252267 tb.Fatalf("WantErr: could not apply options: %v", optionErr)
268268+253269 return
254270 }
255271 }
···259275 reason string
260276 wanted error
261277 )
278278+262279 if want {
263280 reason = fmt.Sprintf("Wanted an error but got %v", err)
264281 wanted = errors.New("error")
···266283 reason = fmt.Sprintf("Got an unexpected error: %v", err)
267284 wanted = nil
268285 }
286286+269287 cfg.reason = reason
270288 fail := failure[error]{
271289 got: err,
···282300// test.True(t, false) // Fails
283301func True(tb testing.TB, got bool, options ...Option) {
284302 tb.Helper()
303303+285304 cfg := defaultConfig()
286305 cfg.title = "Not True"
287306288307 for _, option := range options {
289308 if err := option.apply(&cfg); err != nil {
290309 tb.Fatalf("True: could not apply options: %v", err)
310310+291311 return
292312 }
293313 }
···308328// test.False(t, true) // Fails
309329func False(tb testing.TB, got bool, options ...Option) {
310330 tb.Helper()
331331+311332 cfg := defaultConfig()
312333 cfg.title = "Not False"
313334314335 for _, option := range options {
315336 if err := option.apply(&cfg); err != nil {
316337 tb.Fatalf("False: could not apply options: %v", err)
338338+317339 return
318340 }
319341 }
···395417 stderrCapture := make(chan string)
396418397419 var wg sync.WaitGroup
398398- wg.Add(2) //nolint: mnd
420420+421421+ wg.Add(2) //nolint: mnd // 2 because stdout and stderr
399422400423 // Copy in goroutines to avoid blocking
401424 go func(wg *sync.WaitGroup) {
···403426 close(stdoutCapture)
404427 wg.Done()
405428 }()
429429+406430 buf := &bytes.Buffer{}
407431 if _, err := io.Copy(buf, stdoutReader); err != nil {
408432 tb.Fatalf("CaptureOutput: failed to copy from stdout reader: %v", err)
···415439 close(stderrCapture)
416440 wg.Done()
417441 }()
442442+418443 buf := &bytes.Buffer{}
419444 if _, err := io.Copy(buf, stderrReader); err != nil {
420445 tb.Fatalf("CaptureOutput: failed to copy from stderr reader: %v", err)
···428453 }
429454430455 // Close the writers
431431- stdoutWriter.Close()
432432- stderrWriter.Close()
456456+ stdoutCloseErr := stdoutWriter.Close()
457457+ if stdoutCloseErr != nil {
458458+ tb.Fatalf("CaptureOutput: could not close stdout pipe: %v", stdoutCloseErr)
459459+ }
460460+461461+ stderrCloseErr := stderrWriter.Close()
462462+ if stderrCloseErr != nil {
463463+ tb.Fatalf("CaptueOutput: could not close stderr pipe: %v", stderrCloseErr)
464464+ }
433465434466 capturedStdout := <-stdoutCapture
435467 capturedStderr := <-stderrCapture
···4848// Second, the name is frequently interpreted as meaning that you have
4949// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm,
5050// when in fact the algorithm is faster than the standard one.
5151-func Diff( //nolint: gocyclo
5151+func Diff(
5252 oldName string,
5353 old []byte,
5454 newName string,
5555- new []byte, //nolint: predeclared
5555+ new []byte,
5656) []byte {
5757 if bytes.Equal(old, new) {
5858 return nil
5959 }
6060+6061 x := lines(old)
6162 y := lines(new)
62636364 // Print diff header.
6465 var out bytes.Buffer
6666+6567 fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
6668 fmt.Fprintf(&out, "--- %s\n", oldName)
6769 fmt.Fprintf(&out, "+++ %s\n", newName)
···7880 count pair // number of lines from each side in current chunk
7981 ctext []string // lines for current chunk
8082 )
8383+8184 for _, m := range tgs(x, y) {
8285 if m.x < done.x {
8386 // Already handled scanning forward from earlier match.
···9396 start.x--
9497 start.y--
9598 }
9999+96100 end := m
97101 for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
98102 end.x++
···105109 ctext = append(ctext, "- "+s)
106110 count.x++
107111 }
112112+108113 for _, s := range y[done.y:start.y] {
109114 ctext = append(ctext, "+ "+s)
110115 count.y++
···120125 count.x++
121126 count.y++
122127 }
128128+123129 done = end
130130+124131 continue
125132 }
126133···130137 if n > C {
131138 n = C
132139 }
140140+133141 for _, s := range x[start.x : start.x+n] {
134142 ctext = append(ctext, " "+s)
135143 count.x++
136144 count.y++
137145 }
146146+138147 done = pair{start.x + n, start.y + n}
139148140149 // Format and emit chunk.
···143152 if count.x > 0 {
144153 chunk.x++
145154 }
155155+146156 if count.y > 0 {
147157 chunk.y++
148158 }
159159+149160 fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
161161+150162 for _, s := range ctext {
151163 out.WriteString(s)
152164 }
165165+153166 count.x = 0
154167 count.y = 0
155168 ctext = ctext[:0]
···167180 count.x++
168181 count.y++
169182 }
183183+170184 done = end
171185 }
172186···185199 // using the same text as BSD/GNU diff (including the leading backslash).
186200 l[len(l)-1] += "\n\\ No newline at end of file\n"
187201 }
202202+188203 return l
189204}
190205···207222 m[s] = c - 1
208223 }
209224 }
225225+210226 for _, s := range y {
211227 if c := m[s]; c > -8 {
212212- m[s] = c - 4 //nolint: mnd
228228+ m[s] = c - 4
213229 }
214230 }
215231···220236 // yi[i] = increasing indexes of unique strings in y.
221237 // inv[i] = index j such that x[xi[i]] = y[yi[j]].
222238 var xi, yi, inv []int
239239+223240 for i, s := range y {
224241 if m[s] == -1+-4 {
225242 m[s] = len(yi)
226243 yi = append(yi, i)
227244 }
228245 }
246246+229247 for i, s := range x {
230248 if j, ok := m[s]; ok && j >= 0 {
231249 xi = append(xi, i)
···241259 n := len(xi)
242260 T := make([]int, n)
243261 L := make([]int, n)
262262+244263 for i := range T {
245264 T[i] = n + 1
246265 }
247247- for i := 0; i < n; i++ {
266266+267267+ for i := range n {
248268 k := sort.Search(n, func(k int) bool {
249269 return T[k] >= J[i]
250270 })
251271 T[k] = J[i]
252272 L[i] = k + 1
253273 }
274274+254275 k := 0
255276 for _, v := range L {
256277 if k < v {
257278 k = v
258279 }
259280 }
260260- seq := make([]pair, 2+k) //nolint:mnd
281281+282282+ seq := make([]pair, 2+k)
261283 seq[1+k] = pair{len(x), len(y)} // sentinel at end
284284+262285 lastj := n
263286 for i := n - 1; i >= 0; i-- {
264287 if L[i] == k && J[i] < lastj {
···266289 k--
267290 }
268291 }
292292+269293 seq[0] = pair{0, 0} // sentinel at start
294294+270295 return seq
271296}
+5
internal/diff/diff_test.go
···1717func clean(text []byte) []byte {
1818 text = bytes.ReplaceAll(text, []byte("$\n"), []byte("\n"))
1919 text = bytes.TrimSuffix(text, []byte("^D\n"))
2020+2021 return text
2122}
2223···2526 if err != nil {
2627 t.Fatalf("could not glob txtar files: %v", err)
2728 }
2929+2830 if len(files) == 0 {
2931 t.Fatalf("no testdata")
3032 }
···3739 }
3840 // Stupid windows
3941 contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n"))
4242+4043 archive := txtar.Parse(contents)
4144 if len(archive.Files) != 3 || archive.Files[2].Name != "diff" {
4245 t.Fatalf("%s: want three files, third named \"diff\", got: %v", file, archive.Files)
4346 }
4747+4448 diffs := diff.Diff(
4549 archive.Files[0].Name,
4650 clean(archive.Files[0].Data),
···4852 clean(archive.Files[1].Data),
4953 )
5054 want := clean(archive.Files[2].Data)
5555+5156 if !bytes.Equal(diffs, want) {
5257 t.Fatalf("%s: have:\n%s\nwant:\n%s\n%s", file,
5358 diffs, want, diff.Diff("have", diffs, "want", want))