github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/jsonnet/find_importers_test.go (about)

     1  package jsonnet
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/grafana/tanka/pkg/jsonnet/jpath"
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  type findImportersTestCase struct {
    14  	name              string
    15  	files             []string
    16  	expectedImporters []string
    17  	expectedErr       error
    18  }
    19  
    20  func (tc findImportersTestCase) run(t testing.TB) {
    21  	importers, err := FindImporterForFiles("testdata/findImporters", tc.files)
    22  
    23  	if tc.expectedErr != nil {
    24  		require.EqualError(t, err, tc.expectedErr.Error())
    25  	} else {
    26  		require.NoError(t, err)
    27  		require.Equal(t, tc.expectedImporters, importers)
    28  	}
    29  }
    30  
    31  func findImportersTestCases(t testing.TB) []findImportersTestCase {
    32  	t.Helper()
    33  
    34  	return []findImportersTestCase{
    35  		{
    36  			name:              "no files",
    37  			files:             []string{},
    38  			expectedImporters: nil,
    39  		},
    40  		{
    41  			name:        "invalid file",
    42  			files:       []string{"testdata/findImporters/does-not-exist.jsonnet"},
    43  			expectedErr: fmt.Errorf("lstat %s: no such file or directory", absPath(t, "testdata/findImporters/does-not-exist.jsonnet")),
    44  		},
    45  		{
    46  			name:              "project with no imports",
    47  			files:             []string{"testdata/findImporters/environments/no-imports/main.jsonnet"},
    48  			expectedImporters: []string{absPath(t, "testdata/findImporters/environments/no-imports/main.jsonnet")}, // itself only
    49  		},
    50  		{
    51  			name:              "local import",
    52  			files:             []string{"testdata/findImporters/environments/imports-locals-and-vendored/local-file1.libsonnet"},
    53  			expectedImporters: []string{absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet")},
    54  		},
    55  		{
    56  			name:              "local import with relative path",
    57  			files:             []string{"testdata/findImporters/environments/imports-locals-and-vendored/local-file2.libsonnet"},
    58  			expectedImporters: []string{absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet")},
    59  		},
    60  		{
    61  			name:  "lib imported through chain",
    62  			files: []string{"testdata/findImporters/lib/lib1/main.libsonnet"},
    63  			expectedImporters: []string{
    64  				absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"),
    65  			},
    66  		},
    67  		{
    68  			name:  "vendored lib imported through chain + directly",
    69  			files: []string{"testdata/findImporters/vendor/vendored/main.libsonnet"},
    70  			expectedImporters: []string{
    71  				absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"),
    72  				absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"),
    73  				absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"),
    74  			},
    75  		},
    76  		{
    77  			name:  "vendored lib found through symlink", // expect same result as previous test
    78  			files: []string{"testdata/findImporters/vendor/vendor-symlinked/main.libsonnet"},
    79  			expectedImporters: []string{
    80  				absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"),
    81  				absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"),
    82  				absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"),
    83  			},
    84  		},
    85  		{
    86  			name:  "text file",
    87  			files: []string{"testdata/findImporters/vendor/vendored/text-file.txt"},
    88  			expectedImporters: []string{
    89  				absPath(t, "testdata/findImporters/environments/imports-lib-and-vendored-through-chain/main.jsonnet"),
    90  				absPath(t, "testdata/findImporters/environments/imports-locals-and-vendored/main.jsonnet"),
    91  				absPath(t, "testdata/findImporters/environments/imports-symlinked-vendor/main.jsonnet"),
    92  			},
    93  		},
    94  		{
    95  			name:  "relative imported environment",
    96  			files: []string{"testdata/findImporters/environments/relative-imported/main.jsonnet"},
    97  			expectedImporters: []string{
    98  				absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"),
    99  				absPath(t, "testdata/findImporters/environments/relative-imported/main.jsonnet"), // itself, it's a main file
   100  			},
   101  		},
   102  		{
   103  			name:  "relative imported environment with doubled '..'",
   104  			files: []string{"testdata/findImporters/environments/relative-imported2/main.jsonnet"},
   105  			expectedImporters: []string{
   106  				absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"),
   107  				absPath(t, "testdata/findImporters/environments/relative-imported2/main.jsonnet"), // itself, it's a main file
   108  			},
   109  		},
   110  		{
   111  			name:  "relative imported text file",
   112  			files: []string{"testdata/findImporters/other-files/test.txt"},
   113  			expectedImporters: []string{
   114  				absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"),
   115  			},
   116  		},
   117  		{
   118  			name:  "relative imported text file with doubled '..'",
   119  			files: []string{"testdata/findImporters/other-files/test2.txt"},
   120  			expectedImporters: []string{
   121  				absPath(t, "testdata/findImporters/environments/relative-import/main.jsonnet"),
   122  			},
   123  		},
   124  		{
   125  			name: "vendor override in env: override vendor used",
   126  			files: []string{
   127  				"testdata/findImporters/environments/vendor-override-in-env/vendor/vendor-override-in-env/main.libsonnet",
   128  			},
   129  			expectedImporters: []string{
   130  				absPath(t, "testdata/findImporters/environments/vendor-override-in-env/main.jsonnet"),
   131  			},
   132  		},
   133  		{
   134  			name: "vendor override in env: global vendor unused",
   135  			files: []string{
   136  				"testdata/findImporters/vendor/vendor-override-in-env/main.libsonnet",
   137  			},
   138  			expectedImporters: nil,
   139  		},
   140  		{
   141  			name:  "imported file in lib relative to env",
   142  			files: []string{"testdata/findImporters/environments/lib-import-relative-to-env/file-to-import.libsonnet"},
   143  			expectedImporters: []string{
   144  				absPath(t, "testdata/findImporters/environments/lib-import-relative-to-env/folder1/folder2/main.jsonnet"),
   145  			},
   146  		},
   147  		{
   148  			name: "unused deleted file",
   149  			files: []string{
   150  				"deleted:testdata/findImporters/vendor/deleted-vendor/main.libsonnet",
   151  			},
   152  			expectedImporters: nil,
   153  		},
   154  		{
   155  			name: "deleted local path that is still potentially imported",
   156  			files: []string{
   157  				"deleted:testdata/findImporters/environments/using-deleted-stuff/my-import-dir/main.libsonnet",
   158  			},
   159  			expectedImporters: []string{
   160  				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
   161  			},
   162  		},
   163  		{
   164  			name: "deleted lib that is still potentially imported",
   165  			files: []string{
   166  				"deleted:testdata/findImporters/lib/my-import-dir/main.libsonnet",
   167  			},
   168  			expectedImporters: []string{
   169  				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
   170  			},
   171  		},
   172  		{
   173  			name: "deleted vendor that is still potentially imported",
   174  			files: []string{
   175  				"deleted:testdata/findImporters/vendor/my-import-dir/main.libsonnet",
   176  			},
   177  			expectedImporters: []string{
   178  				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
   179  			},
   180  		},
   181  		{
   182  			name: "deleted lib that is still potentially imported, relative path from root",
   183  			files: []string{
   184  				"deleted:lib/my-import-dir/main.libsonnet",
   185  			},
   186  			expectedImporters: []string{
   187  				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
   188  			},
   189  		},
   190  		{
   191  			// All files in an environment are considered to be imported by the main file, so the same should apply for deleted files
   192  			name: "deleted dir in environment",
   193  			files: []string{
   194  				"deleted:testdata/findImporters/environments/no-imports/deleted-dir/deleted-file.libsonnet",
   195  			},
   196  			expectedImporters: []string{
   197  				absPath(t, "testdata/findImporters/environments/no-imports/main.jsonnet"),
   198  			},
   199  		},
   200  		{
   201  			name: "imports through a main file are followed",
   202  			files: []string{
   203  				"testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet",
   204  			},
   205  			expectedImporters: []string{
   206  				absPath(t, "testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet"),
   207  				absPath(t, "testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet"),
   208  			},
   209  		},
   210  	}
   211  }
   212  
   213  func TestFindImportersForFiles(t *testing.T) {
   214  	// Sanity check
   215  	// Make sure the main files all eval correctly
   216  	// We want to make sure that the importers command works correctly,
   217  	// but there's no point in testing on invalid jsonnet files
   218  	files, err := FindFiles("testdata", nil)
   219  	require.NoError(t, err)
   220  	require.NotEmpty(t, files)
   221  	for _, file := range files {
   222  		// This project is known to be invalid (as the name suggests)
   223  		if strings.Contains(file, "using-deleted-stuff") {
   224  			continue
   225  		}
   226  
   227  		// Skip non-main files
   228  		if filepath.Base(file) != jpath.DefaultEntrypoint {
   229  			continue
   230  		}
   231  		_, err := EvaluateFile(jsonnetImpl, file, Opts{})
   232  		require.NoError(t, err, "failed to eval %s", file)
   233  	}
   234  
   235  	for _, c := range findImportersTestCases(t) {
   236  		t.Run(c.name, func(t *testing.T) {
   237  			c.run(t)
   238  		})
   239  	}
   240  }
   241  
   242  func BenchmarkFindImporters(b *testing.B) {
   243  	// Create a very large and complex project
   244  	tempDir, err := filepath.EvalSymlinks(b.TempDir())
   245  	require.NoError(b, err)
   246  	generateTestProject(b, tempDir, 100, false)
   247  
   248  	// Run the benchmark
   249  	expectedImporters := []string{filepath.Join(tempDir, "main.jsonnet")}
   250  	b.ResetTimer()
   251  	for i := 0; i < b.N; i++ {
   252  		importersCache = make(map[string][]string)
   253  		jsonnetFilesCache = make(map[string]map[string]*cachedJsonnetFile)
   254  		symlinkCache = make(map[string]string)
   255  		importers, err := FindImporterForFiles(tempDir, []string{filepath.Join(tempDir, "file10.libsonnet")})
   256  
   257  		require.NoError(b, err)
   258  		require.Equal(b, expectedImporters, importers)
   259  	}
   260  }
   261  
   262  func BenchmarkFindImporters_StaticCases(b *testing.B) {
   263  	for _, c := range findImportersTestCases(b) {
   264  		b.Run(c.name, func(b *testing.B) {
   265  			for i := 0; i < b.N; i++ {
   266  				c.run(b)
   267  			}
   268  		})
   269  	}
   270  }
   271  
   272  func absPath(t testing.TB, path string) string {
   273  	t.Helper()
   274  
   275  	abs, err := filepath.Abs(path)
   276  	require.NoError(t, err)
   277  	return abs
   278  }