cuelang.org/go@v0.10.1/cue/format/format_test.go (about) 1 // Copyright 2018 The CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package format 16 17 // TODO: port more of the tests of go/printer 18 19 import ( 20 "io/fs" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25 "testing" 26 27 "github.com/go-quicktest/qt" 28 "golang.org/x/tools/txtar" 29 30 "cuelang.org/go/cue/ast" 31 "cuelang.org/go/cue/parser" 32 "cuelang.org/go/cue/token" 33 "cuelang.org/go/internal" 34 "cuelang.org/go/internal/cuetest" 35 ) 36 37 var ( 38 defaultConfig = newConfig([]Option{}) 39 Fprint = defaultConfig.fprint 40 ) 41 42 func TestFiles(t *testing.T) { 43 txtarFiles, err := filepath.Glob("testdata/*.txtar") 44 qt.Assert(t, qt.IsNil(err)) 45 for _, txtarFile := range txtarFiles { 46 ar, err := txtar.ParseFile(txtarFile) 47 qt.Assert(t, qt.IsNil(err)) 48 49 opts := []Option{TabIndent(true)} 50 for _, word := range strings.Fields(string(ar.Comment)) { 51 switch word { 52 case "simplify": 53 opts = append(opts, Simplify()) 54 case "sort-imports": 55 opts = append(opts, sortImportsOption()) 56 } 57 } 58 59 tfs, err := txtar.FS(ar) 60 qt.Assert(t, qt.IsNil(err)) 61 inputFiles, err := fs.Glob(tfs, "*.input") 62 qt.Assert(t, qt.IsNil(err)) 63 64 for _, inputFile := range inputFiles { 65 goldenFile := strings.TrimSuffix(inputFile, ".input") + ".golden" 66 t.Run(path.Join(txtarFile, inputFile), func(t *testing.T) { 67 src, err := fs.ReadFile(tfs, inputFile) 68 qt.Assert(t, qt.IsNil(err)) 69 70 res, err := Source(src, opts...) 71 qt.Assert(t, qt.IsNil(err)) 72 73 // make sure formatted output is syntactically correct 74 _, err = parser.ParseFile("", res, parser.AllErrors) 75 qt.Assert(t, qt.IsNil(err)) 76 77 // update golden files if necessary 78 // TODO(mvdan): deduplicate this code with UpdateGoldenFiles on txtar files? 79 if cuetest.UpdateGoldenFiles { 80 for i := range ar.Files { 81 file := &ar.Files[i] 82 if file.Name == goldenFile { 83 file.Data = res 84 return 85 } 86 } 87 ar.Files = append(ar.Files, txtar.File{ 88 Name: goldenFile, 89 Data: res, 90 }) 91 return 92 } 93 94 // get golden 95 gld, err := fs.ReadFile(tfs, goldenFile) 96 qt.Assert(t, qt.IsNil(err)) 97 98 // formatted source and golden must be the same 99 qt.Assert(t, qt.Equals(string(res), string(gld))) 100 101 // TODO(mvdan): check that all files format in an idempotent way, 102 // i.e. that formatting a golden file results in no changes. 103 }) 104 } 105 if cuetest.UpdateGoldenFiles { 106 err = os.WriteFile(txtarFile, txtar.Format(ar), 0o666) 107 qt.Assert(t, qt.IsNil(err)) 108 } 109 } 110 } 111 112 // Verify that the printer can be invoked during initialization. 113 func init() { 114 const name = "foobar" 115 b, err := Fprint(&ast.Ident{Name: name}) 116 if err != nil { 117 panic(err) // error in test 118 } 119 // in debug mode, the result contains additional information; 120 // ignore it 121 if s := string(b); !debug && s != name { 122 panic("got " + s + ", want " + name) 123 } 124 } 125 126 // TestNodes tests nodes that are invalid CUE, but are accepted by 127 // format. 128 func TestNodes(t *testing.T) { 129 testCases := []struct { 130 name string 131 in ast.Node 132 out string 133 }{{ 134 name: "old-style octal numbers", 135 in: ast.NewLit(token.INT, "0123"), 136 out: "0o123", 137 }, { 138 name: "labels with multi-line strings", 139 in: &ast.Field{ 140 Label: ast.NewLit(token.STRING, 141 `""" 142 foo 143 bar 144 """`, 145 ), 146 Value: ast.NewIdent("goo"), 147 }, 148 out: `"foo\nbar": goo`, 149 }, { 150 name: "foo", 151 in: func() ast.Node { 152 st := ast.NewStruct("version", ast.NewString("foo")) 153 st = ast.NewStruct("info", st) 154 ast.AddComment(st.Elts[0], internal.NewComment(true, "FOO")) 155 return st 156 }(), 157 out: `{ 158 // FOO 159 info: { 160 version: "foo" 161 } 162 }`, 163 }} 164 for _, tc := range testCases { 165 t.Run(tc.name, func(t *testing.T) { 166 b, err := Node(tc.in, Simplify()) 167 if err != nil { 168 t.Fatal(err) 169 } 170 if got := string(b); got != tc.out { 171 t.Errorf("\ngot: %v; want: %v", got, tc.out) 172 } 173 }) 174 } 175 176 } 177 178 // Verify that the printer doesn't crash if the AST contains Bad... nodes. 179 func TestBadNodes(t *testing.T) { 180 const src = "package p\n(" 181 const res = "package p\n\n(_|_)\n" 182 f, err := parser.ParseFile("", src, parser.ParseComments) 183 if err == nil { 184 t.Error("expected illegal program") // error in test 185 } 186 b, _ := Fprint(f) 187 if string(b) != res { 188 t.Errorf("got %q, expected %q", string(b), res) 189 } 190 } 191 func TestPackage(t *testing.T) { 192 f := &ast.File{ 193 Decls: []ast.Decl{ 194 &ast.Package{Name: ast.NewIdent("foo")}, 195 &ast.EmbedDecl{ 196 Expr: &ast.BasicLit{ 197 Kind: token.INT, 198 ValuePos: token.NoSpace.Pos(), 199 Value: "1", 200 }, 201 }, 202 }, 203 } 204 b, err := Node(f) 205 if err != nil { 206 t.Fatal(err) 207 } 208 const want = "package foo\n\n1\n" 209 if got := string(b); got != want { 210 t.Errorf("got %q, expected %q", got, want) 211 } 212 } 213 214 // idents is an iterator that returns all idents in f via the result channel. 215 func idents(f *ast.File) <-chan *ast.Ident { 216 v := make(chan *ast.Ident) 217 go func() { 218 ast.Walk(f, func(n ast.Node) bool { 219 if ident, ok := n.(*ast.Ident); ok { 220 v <- ident 221 } 222 return true 223 }, nil) 224 close(v) 225 }() 226 return v 227 } 228 229 // identCount returns the number of identifiers found in f. 230 func identCount(f *ast.File) int { 231 n := 0 232 for range idents(f) { 233 n++ 234 } 235 return n 236 } 237 238 // Verify that the SourcePos mode emits correct //line comments 239 // by testing that position information for matching identifiers 240 // is maintained. 241 func TestSourcePos(t *testing.T) { 242 const src = `package p 243 244 import ( 245 "go/printer" 246 "math" 247 "regexp" 248 ) 249 250 let pi = 3.14 251 let xx = 0 252 t: { 253 x: int 254 y: int 255 z: int 256 u: number 257 v: number 258 w: number 259 } 260 e: a*t.x + b*t.y 261 262 // two extra lines here // ... 263 e2: c*t.z 264 ` 265 266 // parse original 267 f1, err := parser.ParseFile("src", src, parser.ParseComments) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 // pretty-print original 273 b, err := (&config{UseSpaces: true, Tabwidth: 8}).fprint(f1) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 // parse pretty printed original 279 // (//line comments must be interpreted even w/o syntax.ParseComments set) 280 f2, err := parser.ParseFile("", b, parser.AllErrors, parser.ParseComments) 281 if err != nil { 282 t.Fatalf("%s\n%s", err, b) 283 } 284 285 // At this point the position information of identifiers in f2 should 286 // match the position information of corresponding identifiers in f1. 287 288 // number of identifiers must be > 0 (test should run) and must match 289 n1 := identCount(f1) 290 n2 := identCount(f2) 291 if n1 == 0 { 292 t.Fatal("got no idents") 293 } 294 if n2 != n1 { 295 t.Errorf("got %d idents; want %d", n2, n1) 296 } 297 298 // verify that all identifiers have correct line information 299 i2range := idents(f2) 300 for i1 := range idents(f1) { 301 i2 := <-i2range 302 303 if i2 == nil || i1 == nil { 304 t.Fatal("non nil identifiers") 305 } 306 if i2.Name != i1.Name { 307 t.Errorf("got ident %s; want %s", i2.Name, i1.Name) 308 } 309 310 l1 := i1.Pos().Line() 311 l2 := i2.Pos().Line() 312 if l2 != l1 { 313 t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) 314 } 315 } 316 317 if t.Failed() { 318 t.Logf("\n%s", b) 319 } 320 } 321 322 var decls = []string{ 323 "package p\n\n" + `import "fmt"`, 324 "package p\n\n" + "let pi = 3.1415\nlet e = 2.71828\n\nlet x = pi", 325 } 326 327 func TestDeclLists(t *testing.T) { 328 for _, src := range decls { 329 file, err := parser.ParseFile("", src, parser.ParseComments) 330 if err != nil { 331 panic(err) // error in test 332 } 333 334 b, err := Fprint(file.Decls) // only print declarations 335 if err != nil { 336 panic(err) // error in test 337 } 338 339 out := string(b) 340 341 if out != src { 342 t.Errorf("\ngot : %q\nwant: %q\n", out, src) 343 } 344 } 345 } 346 347 func TestIncorrectIdent(t *testing.T) { 348 testCases := []struct { 349 ident string 350 out string 351 }{ 352 {"foo", "foo"}, 353 {"a.b.c", `"a.b.c"`}, 354 {"for", "for"}, 355 } 356 for _, tc := range testCases { 357 t.Run(tc.ident, func(t *testing.T) { 358 b, _ := Node(&ast.Field{Label: ast.NewIdent(tc.ident), Value: ast.NewIdent("A")}) 359 if got, want := string(b), tc.out+`: A`; got != want { 360 t.Errorf("got %q; want %q", got, want) 361 } 362 }) 363 } 364 } 365 366 // TextX is a skeleton test that can be filled in for debugging one-off cases. 367 // Do not remove. 368 func TestX(t *testing.T) { 369 t.Skip() 370 const src = ` 371 372 ` 373 b, err := Source([]byte(src), Simplify()) 374 if err != nil { 375 t.Error(err) 376 } 377 _ = b 378 t.Error("\n", string(b)) 379 }