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  }