| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package unify |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "slices" |
| "strings" |
| "testing" |
| |
| "gopkg.in/yaml.v3" |
| ) |
| |
| func TestUnify(t *testing.T) { |
| paths, err := filepath.Glob("testdata/*") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if len(paths) == 0 { |
| t.Fatal("no testdata found") |
| } |
| for _, path := range paths { |
| // Skip paths starting with _ so experimental files can be added. |
| base := filepath.Base(path) |
| if base[0] == '_' { |
| continue |
| } |
| if !strings.HasSuffix(base, ".yaml") { |
| t.Errorf("non-.yaml file in testdata: %s", base) |
| continue |
| } |
| base = strings.TrimSuffix(base, ".yaml") |
| |
| t.Run(base, func(t *testing.T) { |
| testUnify(t, path) |
| }) |
| } |
| } |
| |
| func testUnify(t *testing.T, path string) { |
| f, err := os.Open(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer f.Close() |
| |
| type testCase struct { |
| Skip bool |
| Name string |
| Unify []Closure |
| Want yaml.Node |
| All yaml.Node |
| } |
| dec := yaml.NewDecoder(f) |
| |
| for i := 0; ; i++ { |
| var tc testCase |
| err := dec.Decode(&tc) |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| name := tc.Name |
| if name == "" { |
| name = fmt.Sprint(i) |
| } |
| |
| t.Run(name, func(t *testing.T) { |
| if tc.Skip { |
| t.Skip("skip: true set in test case") |
| } |
| |
| defer func() { |
| p := recover() |
| if p != nil || t.Failed() { |
| // Redo with a trace |
| // |
| // TODO: Use t.Output() in Go 1.25. |
| var buf bytes.Buffer |
| Debug.UnifyLog = &buf |
| func() { |
| defer func() { |
| // If the original unify panicked, the second one |
| // probably will, too. Ignore it and let the first panic |
| // bubble. |
| recover() |
| }() |
| Unify(tc.Unify...) |
| }() |
| Debug.UnifyLog = nil |
| t.Logf("Trace:\n%s", buf.String()) |
| } |
| if p != nil { |
| panic(p) |
| } |
| }() |
| |
| // Unify the test cases |
| // |
| // TODO: Try reordering the inputs also |
| c, err := Unify(tc.Unify...) |
| if err != nil { |
| // TODO: Tests of errors |
| t.Fatal(err) |
| } |
| |
| // Encode the result back to YAML so we can check if it's structurally |
| // equal. |
| clean := func(val any) *yaml.Node { |
| var node yaml.Node |
| node.Encode(val) |
| for n := range allYamlNodes(&node) { |
| // Canonicalize the style. There may be other style flags we need to |
| // muck with. |
| n.Style &^= yaml.FlowStyle |
| n.HeadComment = "" |
| n.LineComment = "" |
| n.FootComment = "" |
| } |
| return &node |
| } |
| check := func(gotVal any, wantNode *yaml.Node) { |
| got, err := yaml.Marshal(clean(gotVal)) |
| if err != nil { |
| t.Fatalf("Encoding Value back to yaml failed: %s", err) |
| } |
| want, err := yaml.Marshal(clean(wantNode)) |
| if err != nil { |
| t.Fatalf("Encoding Want back to yaml failed: %s", err) |
| } |
| |
| if !bytes.Equal(got, want) { |
| t.Errorf("%s:%d:\nwant:\n%sgot\n%s", f.Name(), wantNode.Line, want, got) |
| } |
| } |
| if tc.Want.Kind != 0 { |
| check(c.val, &tc.Want) |
| } |
| if tc.All.Kind != 0 { |
| fVal := slices.Collect(c.All()) |
| check(fVal, &tc.All) |
| } |
| }) |
| } |
| } |