Source file src/simd/_gen/unify/yaml_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package unify
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"iter"
    11  	"log"
    12  	"strings"
    13  	"testing"
    14  	"testing/fstest"
    15  
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  func mustParse(expr string) Closure {
    20  	var c Closure
    21  	if err := yaml.Unmarshal([]byte(expr), &c); err != nil {
    22  		panic(err)
    23  	}
    24  	return c
    25  }
    26  
    27  func oneValue(t *testing.T, c Closure) *Value {
    28  	t.Helper()
    29  	var v *Value
    30  	var i int
    31  	for v = range c.All() {
    32  		i++
    33  	}
    34  	if i != 1 {
    35  		t.Fatalf("expected 1 value, got %d", i)
    36  	}
    37  	return v
    38  }
    39  
    40  func printYaml(val any) {
    41  	fmt.Println(prettyYaml(val))
    42  }
    43  
    44  func prettyYaml(val any) string {
    45  	b, err := yaml.Marshal(val)
    46  	if err != nil {
    47  		panic(err)
    48  	}
    49  	var node yaml.Node
    50  	if err := yaml.Unmarshal(b, &node); err != nil {
    51  		panic(err)
    52  	}
    53  
    54  	// Map lines to start offsets. We'll use this to figure out when nodes are
    55  	// "small" and should use inline style.
    56  	lines := []int{-1, 0}
    57  	for pos := 0; pos < len(b); {
    58  		next := bytes.IndexByte(b[pos:], '\n')
    59  		if next == -1 {
    60  			break
    61  		}
    62  		pos += next + 1
    63  		lines = append(lines, pos)
    64  	}
    65  	lines = append(lines, len(b))
    66  
    67  	// Strip comments and switch small nodes to inline style
    68  	cleanYaml(&node, lines, len(b))
    69  
    70  	b, err = yaml.Marshal(&node)
    71  	if err != nil {
    72  		panic(err)
    73  	}
    74  	return string(b)
    75  }
    76  
    77  func cleanYaml(node *yaml.Node, lines []int, endPos int) {
    78  	node.HeadComment = ""
    79  	node.FootComment = ""
    80  	node.LineComment = ""
    81  
    82  	for i, n2 := range node.Content {
    83  		end2 := endPos
    84  		if i < len(node.Content)-1 {
    85  			end2 = lines[node.Content[i+1].Line]
    86  		}
    87  		cleanYaml(n2, lines, end2)
    88  	}
    89  
    90  	// Use inline style?
    91  	switch node.Kind {
    92  	case yaml.MappingNode, yaml.SequenceNode:
    93  		if endPos-lines[node.Line] < 40 {
    94  			node.Style = yaml.FlowStyle
    95  		}
    96  	}
    97  }
    98  
    99  func allYamlNodes(n *yaml.Node) iter.Seq[*yaml.Node] {
   100  	return func(yield func(*yaml.Node) bool) {
   101  		if !yield(n) {
   102  			return
   103  		}
   104  		for _, n2 := range n.Content {
   105  			for n3 := range allYamlNodes(n2) {
   106  				if !yield(n3) {
   107  					return
   108  				}
   109  			}
   110  		}
   111  	}
   112  }
   113  
   114  func TestRoundTripString(t *testing.T) {
   115  	// Check that we can round-trip a string with regexp meta-characters in it.
   116  	const y = `!string test*`
   117  	t.Logf("input:\n%s", y)
   118  
   119  	v1 := oneValue(t, mustParse(y))
   120  	var buf1 strings.Builder
   121  	enc := yaml.NewEncoder(&buf1)
   122  	if err := enc.Encode(v1); err != nil {
   123  		log.Fatal(err)
   124  	}
   125  	enc.Close()
   126  	t.Logf("after parse 1:\n%s", buf1.String())
   127  
   128  	v2 := oneValue(t, mustParse(buf1.String()))
   129  	var buf2 strings.Builder
   130  	enc = yaml.NewEncoder(&buf2)
   131  	if err := enc.Encode(v2); err != nil {
   132  		log.Fatal(err)
   133  	}
   134  	enc.Close()
   135  	t.Logf("after parse 2:\n%s", buf2.String())
   136  
   137  	if buf1.String() != buf2.String() {
   138  		t.Fatal("parse 1 and parse 2 differ")
   139  	}
   140  }
   141  
   142  func TestEmptyString(t *testing.T) {
   143  	// Regression test. Make sure an empty string is parsed as an exact string,
   144  	// not a regexp.
   145  	const y = `""`
   146  	t.Logf("input:\n%s", y)
   147  
   148  	v1 := oneValue(t, mustParse(y))
   149  	if !v1.Exact() {
   150  		t.Fatal("expected exact string")
   151  	}
   152  }
   153  
   154  func TestImport(t *testing.T) {
   155  	// Test a basic import
   156  	main := strings.NewReader("!import x/y.yaml")
   157  	fs := fstest.MapFS{
   158  		// Test a glob import with a relative path
   159  		"x/y.yaml":   {Data: []byte("!import y/*.yaml")},
   160  		"x/y/z.yaml": {Data: []byte("42")},
   161  	}
   162  	cl, err := Read(main, "x.yaml", ReadOpts{FS: fs})
   163  	if err != nil {
   164  		t.Fatal(err)
   165  	}
   166  	x := 42
   167  	checkDecode(t, oneValue(t, cl), &x)
   168  }
   169  
   170  func TestImportEscape(t *testing.T) {
   171  	// Make sure an import can't escape its subdirectory.
   172  	main := strings.NewReader("!import x/y.yaml")
   173  	fs := fstest.MapFS{
   174  		"x/y.yaml": {Data: []byte("!import ../y/*.yaml")},
   175  		"y/z.yaml": {Data: []byte("42")},
   176  	}
   177  	_, err := Read(main, "x.yaml", ReadOpts{FS: fs})
   178  	if err == nil {
   179  		t.Fatal("relative !import should have failed")
   180  	}
   181  	if !strings.Contains(err.Error(), "must not contain") {
   182  		t.Fatalf("unexpected error %v", err)
   183  	}
   184  }
   185  
   186  func TestImportScope(t *testing.T) {
   187  	// Test that imports have different variable scopes.
   188  	main := strings.NewReader("[!import y.yaml, !import y.yaml]")
   189  	fs := fstest.MapFS{
   190  		"y.yaml": {Data: []byte("$v")},
   191  	}
   192  	cl1, err := Read(main, "x.yaml", ReadOpts{FS: fs})
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	cl2 := mustParse("[1, 2]")
   197  	res, err := Unify(cl1, cl2)
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  	checkDecode(t, oneValue(t, res), []int{1, 2})
   202  }
   203  

View as plain text