github.com/samlitowitz/goimportcycle@v1.0.9/internal/dot/marshal_test.go (about) 1 package dot_test 2 3 import ( 4 "context" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "os" 9 "path/filepath" 10 "runtime" 11 "testing" 12 13 "github.com/samlitowitz/goimportcycle/internal/config" 14 "github.com/samlitowitz/goimportcycle/internal/dot" 15 16 internalAST "github.com/samlitowitz/goimportcycle/internal/ast" 17 18 "github.com/google/go-cmp/cmp" 19 ) 20 21 func TestMarshal_PackageNameWithDotsOrDashes_FileScope(t *testing.T) { 22 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L600 23 // -- START -- // 24 if runtime.GOOS == "ios" { 25 restore := chtmpdir(t) 26 defer restore() 27 } 28 29 tmpDir := t.TempDir() 30 31 origDir, err := os.Getwd() 32 if err != nil { 33 t.Fatal("finding working dir:", err) 34 } 35 if err = os.Chdir(tmpDir); err != nil { 36 t.Fatal("entering temp dir:", err) 37 } 38 defer os.Chdir(origDir) 39 // -- END -- // 40 41 tree := &Node{ 42 "testdata", 43 []*Node{ 44 // simple: a -> b -> a 45 { 46 "simple", 47 []*Node{ 48 { 49 "main.go", 50 nil, 51 "main", 52 ` 53 package main 54 55 import a "example.com/simple/a-a" 56 57 func main() { 58 a.AFn() 59 } 60 61 `, 62 }, 63 { 64 "a-a", 65 []*Node{ 66 { 67 "a.go", 68 nil, 69 "a_a", 70 ` 71 package a_a 72 73 import b "example.com/simple/b.b" 74 75 func AFn() { 76 b.BFn() 77 } 78 `, 79 }, 80 }, 81 "", 82 "", 83 }, 84 { 85 "b.b", 86 []*Node{ 87 { 88 "b.go", 89 nil, 90 "b_b", 91 ` 92 package b_b 93 94 import a "example.com/simple/a-a" 95 96 func BFn() { 97 a.AFn() 98 } 99 `, 100 }, 101 }, 102 "", 103 "", 104 }, 105 }, 106 "", 107 "", 108 }, 109 }, 110 "", 111 "", 112 } 113 makeTree(t, tree) 114 115 for _, treeNode := range tree.entries { 116 testCase := treeNode.name 117 dirOut := make(chan string) 118 depVis, nodeOut := internalAST.NewDependencyVisitor() 119 builder := internalAST.NewPrimitiveBuilder( 120 "example.com/"+testCase, 121 tmpDir+string(os.PathSeparator)+"testdata"+string(os.PathSeparator)+testCase, 122 ) 123 124 directoryPathsInOrder := []string{} 125 walkTree( 126 treeNode, 127 treeNode.name, 128 func(path string, n *Node) { 129 if n.entries == nil { 130 return 131 } 132 directoryPathsInOrder = append( 133 directoryPathsInOrder, 134 tmpDir+string(os.PathSeparator)+"testdata"+string(os.PathSeparator)+path, 135 ) 136 }, 137 ) 138 139 ctx, cancel := context.WithCancel(context.Background()) 140 141 go func() { 142 for _, dirPath := range directoryPathsInOrder { 143 dirOut <- dirPath 144 } 145 close(dirOut) 146 }() 147 148 go func() { 149 for { 150 select { 151 case dirPath, ok := <-dirOut: 152 if !ok { 153 depVis.Close() 154 return 155 } 156 fset := token.NewFileSet() 157 pkgs, err := parser.ParseDir(fset, dirPath, nil, 0) 158 if err != nil { 159 cancel() 160 t.Fatalf("%s: %s", testCase, err) 161 } 162 163 for _, pkg := range pkgs { 164 ast.Walk(depVis, pkg) 165 } 166 167 case <-ctx.Done(): 168 depVis.Close() 169 return 170 } 171 } 172 }() 173 174 go func() { 175 for { 176 select { 177 case astNode, ok := <-nodeOut: 178 if !ok { 179 cancel() 180 return 181 } 182 err = builder.AddNode(astNode) 183 if err != nil { 184 cancel() 185 t.Error(err) 186 } 187 case <-ctx.Done(): 188 return 189 } 190 } 191 }() 192 <-ctx.Done() 193 194 err := builder.MarkupImportCycles() 195 if err != nil { 196 t.Fatalf( 197 "%s: error marking up import cycles: %s", 198 testCase, 199 err, 200 ) 201 } 202 203 for _, file := range builder.Files() { 204 if file.Package.Name == "main" { 205 continue 206 } 207 if !file.Package.InImportCycle { 208 t.Errorf( 209 "%s: %s: %s: not in expected import cycle", 210 testCase, 211 file.AbsPath, 212 file.Package.Name, 213 ) 214 } 215 if !file.InImportCycle { 216 t.Errorf( 217 "%s: %s: not in expected import cycle", 218 testCase, 219 file.AbsPath, 220 ) 221 } 222 } 223 expected := `digraph { 224 labelloc="t"; 225 label="example.com/simple"; 226 rankdir="TB"; 227 node [shape="rect"]; 228 229 subgraph "cluster_pkg_a_a" { 230 label="a-a"; 231 style="filled"; 232 fontcolor="#ff0000"; 233 fillcolor="#ffffff"; 234 235 "pkg_a_a_file_a" [label="a.go", style="filled", fontcolor="#ff0000", fillcolor="#ffffff"]; 236 }; 237 238 subgraph "cluster_pkg_b_b" { 239 label="b.b"; 240 style="filled"; 241 fontcolor="#ff0000"; 242 fillcolor="#ffffff"; 243 244 "pkg_b_b_file_b" [label="b.go", style="filled", fontcolor="#ff0000", fillcolor="#ffffff"]; 245 }; 246 247 subgraph "cluster_pkg_main" { 248 label="main"; 249 style="filled"; 250 fontcolor="#000000"; 251 fillcolor="#ffffff"; 252 253 "pkg_main_file_main" [label="main.go", style="filled", fontcolor="#000000", fillcolor="#ffffff"]; 254 }; 255 256 "pkg_a_a_file_a" -> "pkg_b_b_file_b" [color="#ff0000"]; 257 "pkg_b_b_file_b" -> "pkg_a_a_file_a" [color="#ff0000"]; 258 "pkg_main_file_main" -> "pkg_a_a_file_a" [color="#000000"]; 259 } 260 ` 261 262 cfg := config.Default() 263 cfg.Resolution = config.FileResolution 264 actual, err := dot.Marshal(cfg, "example.com/"+testCase, builder.Packages()) 265 if err != nil { 266 t.Fatal(err) 267 } 268 if !cmp.Equal(expected, string(actual)) { 269 t.Fatal(cmp.Diff(expected, string(actual))) 270 } 271 } 272 } 273 274 func TestMarshal_PackageNameWithDotsOrDashes_PackageScope(t *testing.T) { 275 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L600 276 // -- START -- // 277 if runtime.GOOS == "ios" { 278 restore := chtmpdir(t) 279 defer restore() 280 } 281 282 tmpDir := t.TempDir() 283 284 origDir, err := os.Getwd() 285 if err != nil { 286 t.Fatal("finding working dir:", err) 287 } 288 if err = os.Chdir(tmpDir); err != nil { 289 t.Fatal("entering temp dir:", err) 290 } 291 defer os.Chdir(origDir) 292 // -- END -- // 293 294 tree := &Node{ 295 "testdata", 296 []*Node{ 297 // simple: a -> b -> a 298 { 299 "simple", 300 []*Node{ 301 { 302 "main.go", 303 nil, 304 "main", 305 ` 306 package main 307 308 import a "example.com/simple/a-a" 309 310 func main() { 311 a.AFn() 312 } 313 314 `, 315 }, 316 { 317 "a-a", 318 []*Node{ 319 { 320 "a.go", 321 nil, 322 "a_a", 323 ` 324 package a_a 325 326 import b "example.com/simple/b.b" 327 328 func AFn() { 329 b.BFn() 330 } 331 `, 332 }, 333 }, 334 "", 335 "", 336 }, 337 { 338 "b.b", 339 []*Node{ 340 { 341 "b.go", 342 nil, 343 "b_b", 344 ` 345 package b_b 346 347 import a "example.com/simple/a-a" 348 349 func BFn() { 350 a.AFn() 351 } 352 `, 353 }, 354 }, 355 "", 356 "", 357 }, 358 }, 359 "", 360 "", 361 }, 362 }, 363 "", 364 "", 365 } 366 makeTree(t, tree) 367 368 for _, treeNode := range tree.entries { 369 testCase := treeNode.name 370 dirOut := make(chan string) 371 depVis, nodeOut := internalAST.NewDependencyVisitor() 372 builder := internalAST.NewPrimitiveBuilder( 373 "example.com/"+testCase, 374 tmpDir+string(os.PathSeparator)+"testdata"+string(os.PathSeparator)+testCase, 375 ) 376 377 directoryPathsInOrder := []string{} 378 walkTree( 379 treeNode, 380 treeNode.name, 381 func(path string, n *Node) { 382 if n.entries == nil { 383 return 384 } 385 directoryPathsInOrder = append( 386 directoryPathsInOrder, 387 tmpDir+string(os.PathSeparator)+"testdata"+string(os.PathSeparator)+path, 388 ) 389 }, 390 ) 391 392 ctx, cancel := context.WithCancel(context.Background()) 393 394 go func() { 395 for _, dirPath := range directoryPathsInOrder { 396 dirOut <- dirPath 397 } 398 close(dirOut) 399 }() 400 401 go func() { 402 for { 403 select { 404 case dirPath, ok := <-dirOut: 405 if !ok { 406 depVis.Close() 407 return 408 } 409 fset := token.NewFileSet() 410 pkgs, err := parser.ParseDir(fset, dirPath, nil, 0) 411 if err != nil { 412 cancel() 413 t.Fatalf("%s: %s", testCase, err) 414 } 415 416 for _, pkg := range pkgs { 417 ast.Walk(depVis, pkg) 418 } 419 420 case <-ctx.Done(): 421 depVis.Close() 422 return 423 } 424 } 425 }() 426 427 go func() { 428 for { 429 select { 430 case astNode, ok := <-nodeOut: 431 if !ok { 432 cancel() 433 return 434 } 435 err = builder.AddNode(astNode) 436 if err != nil { 437 cancel() 438 t.Error(err) 439 } 440 case <-ctx.Done(): 441 return 442 } 443 } 444 }() 445 <-ctx.Done() 446 447 err := builder.MarkupImportCycles() 448 if err != nil { 449 t.Fatalf( 450 "%s: error marking up import cycles: %s", 451 testCase, 452 err, 453 ) 454 } 455 456 for _, file := range builder.Files() { 457 if file.Package.Name == "main" { 458 continue 459 } 460 if !file.Package.InImportCycle { 461 t.Errorf( 462 "%s: %s: %s: not in expected import cycle", 463 testCase, 464 file.AbsPath, 465 file.Package.Name, 466 ) 467 } 468 if !file.InImportCycle { 469 t.Errorf( 470 "%s: %s: not in expected import cycle", 471 testCase, 472 file.AbsPath, 473 ) 474 } 475 } 476 expected := `digraph { 477 labelloc="t"; 478 label="example.com/simple"; 479 rankdir="TB"; 480 node [shape="rect"]; 481 482 "pkg_a_a" [label="a-a", style="filled", fontcolor="#ff0000", fillcolor="#ffffff"]; 483 "pkg_b_b" [label="b.b", style="filled", fontcolor="#ff0000", fillcolor="#ffffff"]; 484 "pkg_main" [label="main", style="filled", fontcolor="#000000", fillcolor="#ffffff"]; 485 "pkg_a_a" -> "pkg_b_b" [color="#ff0000"]; 486 "pkg_b_b" -> "pkg_a_a" [color="#ff0000"]; 487 "pkg_main" -> "pkg_a_a" [color="#000000"]; 488 } 489 ` 490 491 cfg := config.Default() 492 cfg.Resolution = config.PackageResolution 493 actual, err := dot.Marshal(cfg, "example.com/"+testCase, builder.Packages()) 494 if err != nil { 495 t.Fatal(err) 496 } 497 if !cmp.Equal(expected, string(actual)) { 498 t.Fatal(cmp.Diff(expected, string(actual))) 499 } 500 } 501 } 502 503 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L449 504 type Node struct { 505 name string 506 entries []*Node // nil if the entry is a file 507 pkg string // file package belongs to, empty if entry is a directory 508 data string // file content, empty if entry is a directory 509 } 510 511 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L481 512 func walkTree(n *Node, path string, f func(path string, n *Node)) { 513 f(path, n) 514 for _, e := range n.entries { 515 walkTree(e, filepath.Join(path, e.name), f) 516 } 517 } 518 519 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L488 520 func makeTree(t *testing.T, tree *Node) map[string]struct{} { 521 directories := make(map[string]struct{}) 522 walkTree(tree, tree.name, func(path string, n *Node) { 523 if n.entries == nil { 524 fd, err := os.Create(path) 525 if err != nil { 526 t.Errorf("makeTree: %v", err) 527 return 528 } 529 if n.data != "" { 530 _, err = fd.Write([]byte(n.data)) 531 if err != nil { 532 t.Errorf("makeTree: %v", err) 533 return 534 } 535 } 536 fd.Close() 537 } else { 538 os.Mkdir(path, 0770) 539 directories[path] = struct{}{} 540 } 541 }) 542 return directories 543 } 544 545 // REFURL: https://github.com/golang/go/blob/988b718f4130ab5b3ce5a5774e1a58e83c92a163/src/path/filepath/path_test.go#L553 546 func chtmpdir(t *testing.T) (restore func()) { 547 oldwd, err := os.Getwd() 548 if err != nil { 549 t.Fatalf("chtmpdir: %v", err) 550 } 551 d, err := os.MkdirTemp("", "test") 552 if err != nil { 553 t.Fatalf("chtmpdir: %v", err) 554 } 555 if err := os.Chdir(d); err != nil { 556 t.Fatalf("chtmpdir: %v", err) 557 } 558 return func() { 559 if err := os.Chdir(oldwd); err != nil { 560 t.Fatalf("chtmpdir: %v", err) 561 } 562 os.RemoveAll(d) 563 } 564 }