github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/client/tree_whitebox_test.go (about)

     1  package client
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"sort"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata"
    11  	"github.com/google/go-cmp/cmp"
    12  )
    13  
    14  func TestGetTargetRelPath(t *testing.T) {
    15  	execRoot := "/execRoot"
    16  	defaultSym := "symDir/sym"
    17  	tests := []struct {
    18  		desc            string
    19  		path            string
    20  		symMeta         *filemetadata.SymlinkMetadata
    21  		wantErr         bool
    22  		wantRelExecRoot string
    23  		wantRelSymDir   string
    24  	}{
    25  		{
    26  			desc:            "basic",
    27  			path:            defaultSym,
    28  			symMeta:         &filemetadata.SymlinkMetadata{Target: "foo"},
    29  			wantRelExecRoot: "symDir/foo",
    30  			wantRelSymDir:   "foo",
    31  		},
    32  		{
    33  			desc: "relative target path under exec root",
    34  			path: defaultSym,
    35  			// /execRoot/symDir/../dir/foo ==> /execRoot/dir/foo
    36  			symMeta:         &filemetadata.SymlinkMetadata{Target: "../dir/foo"},
    37  			wantRelExecRoot: "dir/foo",
    38  			wantRelSymDir:   "../dir/foo",
    39  		},
    40  		{
    41  			desc: "relative target path escaping exec root",
    42  			path: defaultSym,
    43  			// /execRoot/symDir/../../foo ==> /foo
    44  			symMeta: &filemetadata.SymlinkMetadata{Target: "../../foo"},
    45  			wantErr: true,
    46  		},
    47  		{
    48  			desc: "deeper relative target path",
    49  			path: "base/sub/sym",
    50  			// /execRoot/base/sub/../../foo ==> /execRoot/foo
    51  			symMeta:         &filemetadata.SymlinkMetadata{Target: "../../foo"},
    52  			wantRelExecRoot: "foo",
    53  			wantRelSymDir:   "../../foo",
    54  		},
    55  		{
    56  			desc:            "absolute target path under exec root",
    57  			path:            "base/sym",
    58  			symMeta:         &filemetadata.SymlinkMetadata{Target: execRoot + "/base/foo"},
    59  			wantRelExecRoot: "base/foo",
    60  			wantRelSymDir:   "foo",
    61  		},
    62  		{
    63  			desc:    "abs target to rel target",
    64  			path:    "base/sub1/sub2/sym",
    65  			symMeta: &filemetadata.SymlinkMetadata{Target: execRoot + "/dir/foo"},
    66  			// symlinkAbsDir: /execRoot/base/sub1/sub2
    67  			// targetAbs: /execRoot/dir/foo
    68  			// target rel to symlink: ../../../dir/foo
    69  			wantRelExecRoot: "dir/foo",
    70  			wantRelSymDir:   "../../../dir/foo",
    71  		},
    72  		{
    73  			desc:    "absolute target path escaping exec root",
    74  			path:    defaultSym,
    75  			symMeta: &filemetadata.SymlinkMetadata{Target: "/another/dir/foo"},
    76  			wantErr: true,
    77  		},
    78  	}
    79  
    80  	for _, tc := range tests {
    81  		t.Run(tc.desc, func(t *testing.T) {
    82  			relExecRoot, relSymDir, err := getTargetRelPath(execRoot, tc.path, tc.symMeta.Target)
    83  			if (err != nil) != tc.wantErr {
    84  				t.Errorf("getTargetRelPath(path=%q) error: expected=%v got=%v", tc.path, tc.wantErr, err)
    85  			}
    86  			if err == nil && (relExecRoot != tc.wantRelExecRoot || relSymDir != tc.wantRelSymDir) {
    87  				t.Errorf("getTargetRelPath(path=%q) result: expected=(%v,%v) got=(%v,%v)", tc.path, tc.wantRelExecRoot, tc.wantRelSymDir, relExecRoot, relSymDir)
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  func TestEvalParentSymlinks(t *testing.T) {
    94  	cache := filemetadata.NewSingleFlightCache()
    95  
    96  	mkPath := func(path string) string {
    97  		if filepath.Separator == '/' {
    98  			return path
    99  		}
   100  		return filepath.Join(strings.Split(path, "/")...)
   101  	}
   102  
   103  	testCases := []struct {
   104  		name string
   105  		// List of relative file (no directories) paths with no intermediate symlinks.
   106  		// All paths start under the "root" directory. To go outside, use `..`.
   107  		// To denote a symlink, use the format: "symlink->target", e.g. "a/b->bb".
   108  		// To denote a symlink with an absolute path for its target, prefix the target with a forward slash. E.g. "a/b->/bb".
   109  		// Absolute symlinks also start under "root". To go outside, use `..`, e.g. `a/b->/../root2/bb`.
   110  		fs []string
   111  		// The path that includes intermediate symlinks.
   112  		path         string
   113  		materialize  bool
   114  		wantPath     string
   115  		wantSymlinks []string
   116  		wantErr      bool
   117  	}{
   118  		{
   119  			name: "one_relative_simple",
   120  			fs: []string{
   121  				"wd/a->aa",
   122  				"wd/aa/b.go",
   123  			},
   124  			path:         mkPath("wd/a/b.go"),
   125  			materialize:  false,
   126  			wantPath:     mkPath("wd/aa/b.go"),
   127  			wantSymlinks: []string{mkPath("wd/a")},
   128  		},
   129  		{
   130  			name: "one_relative_basename_symlink",
   131  			fs: []string{
   132  				"wd/a->aa",
   133  				"wd/aa/b.go->c.go",
   134  				"wd/aa/c.go",
   135  			},
   136  			path:         mkPath("wd/a/b.go"),
   137  			materialize:  false,
   138  			wantPath:     mkPath("wd/aa/b.go"),
   139  			wantSymlinks: []string{mkPath("wd/a")},
   140  		},
   141  		{
   142  			name: "one_relative",
   143  			fs: []string{
   144  				"wd/a->../wd2/aa",
   145  				"wd2/aa/b.go",
   146  			},
   147  			path:         mkPath("wd/a/b.go"),
   148  			materialize:  false,
   149  			wantPath:     mkPath("wd2/aa/b.go"),
   150  			wantSymlinks: []string{mkPath("wd/a")},
   151  		},
   152  		{
   153  			name: "one_absolute_simple",
   154  			fs: []string{
   155  				"wd/a->/wd/aa",
   156  				"wd/aa/b.go",
   157  			},
   158  			path:         mkPath("wd/a/b.go"),
   159  			materialize:  false,
   160  			wantPath:     mkPath("wd/aa/b.go"),
   161  			wantSymlinks: []string{mkPath("wd/a")},
   162  		},
   163  		{
   164  			name: "one_absolute",
   165  			fs: []string{
   166  				"wd/a->/wd2/aa",
   167  				"wd2/aa/b.go",
   168  			},
   169  			path:         mkPath("wd/a/b.go"),
   170  			materialize:  false,
   171  			wantPath:     mkPath("wd2/aa/b.go"),
   172  			wantSymlinks: []string{mkPath("wd/a")},
   173  		},
   174  		{
   175  			name: "multiple_relative",
   176  			fs: []string{
   177  				"wd/a->aa",
   178  				"wd/aa/b->bb",
   179  				"wd/aa/bb/c.go",
   180  			},
   181  			path:        mkPath("wd/a/b/c.go"),
   182  			materialize: false,
   183  			wantPath:    mkPath("wd/aa/bb/c.go"),
   184  			wantSymlinks: []string{
   185  				mkPath("wd/a"),
   186  				mkPath("wd/aa/b"),
   187  			},
   188  		},
   189  		{
   190  			name: "multiple_absolute",
   191  			fs: []string{
   192  				"wd/a->/wd/aa",
   193  				"wd/aa/b->/wd/aa/bb",
   194  				"wd/aa/bb/c.go",
   195  			},
   196  			path:        mkPath("wd/a/b/c.go"),
   197  			materialize: false,
   198  			wantPath:    mkPath("wd/aa/bb/c.go"),
   199  			wantSymlinks: []string{
   200  				mkPath("wd/a"),
   201  				mkPath("wd/aa/b"),
   202  			},
   203  		},
   204  		{
   205  			name: "one_relative_materialize_simple",
   206  			fs: []string{
   207  				"wd/a->../../root2/aa",
   208  				"../root2/aa/b.go",
   209  			},
   210  			path:         mkPath("wd/a/b.go"),
   211  			materialize:  true,
   212  			wantPath:     mkPath("wd/a/b.go"),
   213  			wantSymlinks: nil,
   214  		},
   215  		{
   216  			name: "one_absolute_materialize_simple",
   217  			fs: []string{
   218  				"wd/a->/../root2/aa",
   219  				"../root2/aa/b.go",
   220  			},
   221  			path:         mkPath("wd/a/b.go"),
   222  			materialize:  true,
   223  			wantPath:     mkPath("wd/a/b.go"),
   224  			wantSymlinks: nil,
   225  		},
   226  		{
   227  			name: "multiple_relative_materialize_simple",
   228  			fs: []string{
   229  				"wd/a->../../root2/aa",
   230  				"../root2/aa/b->../../root3/aaa/bb",
   231  				"../root3/aaa/bb/c.go",
   232  			},
   233  			path:         mkPath("wd/a/b/c.go"),
   234  			materialize:  true,
   235  			wantPath:     mkPath("wd/a/b/c.go"),
   236  			wantSymlinks: nil,
   237  		},
   238  		{
   239  			name: "multiple_absolute_materialize_simple",
   240  			fs: []string{
   241  				"wd/a->/../root2/aa",
   242  				"../root2/aa/b->/../root3/aaa/bb",
   243  				"../root3/aaa/bb/c.go",
   244  			},
   245  			path:         mkPath("wd/a/b/c.go"),
   246  			materialize:  true,
   247  			wantPath:     mkPath("wd/a/b/c.go"),
   248  			wantSymlinks: nil,
   249  		},
   250  	}
   251  
   252  	for _, tc := range testCases {
   253  		t.Run(tc.name, func(t *testing.T) {
   254  			tmp := t.TempDir()
   255  			root := filepath.Join(tmp, "root")
   256  			for _, p := range tc.fs {
   257  				slParts := strings.Split(p, "->")
   258  				absPath := filepath.Join(root, mkPath(slParts[0]))
   259  				dir := filepath.Dir(absPath)
   260  				err := os.MkdirAll(dir, 0777)
   261  				if err != nil {
   262  					t.Fatalf("unexpected error: %v", err)
   263  				}
   264  
   265  				if len(slParts) > 1 {
   266  					target := mkPath(slParts[1])
   267  					if target[0] == '/' {
   268  						target = filepath.Join(root, target[1:])
   269  					}
   270  					err = os.Symlink(target, absPath)
   271  				} else {
   272  					err = os.WriteFile(absPath, nil, 0777)
   273  				}
   274  				if err != nil {
   275  					t.Fatalf("unexpected error: %v", err)
   276  				}
   277  			}
   278  
   279  			evaledPath, symlinks, err := evalParentSymlinks(root, tc.path, tc.materialize, cache)
   280  			if tc.wantErr && err == nil {
   281  				t.Fatalf("expected an error, but did not get one")
   282  			}
   283  			if !tc.wantErr && err != nil {
   284  				t.Fatalf("unexpected error: %v", err)
   285  			}
   286  			evaledPath = filepath.Clean(evaledPath)
   287  			if evaledPath != tc.wantPath {
   288  				t.Errorf("path mismatch: got %q, want %q", evaledPath, tc.wantPath)
   289  			}
   290  			sort.Strings(symlinks)
   291  			sort.Strings(tc.wantSymlinks)
   292  			if diff := cmp.Diff(tc.wantSymlinks, symlinks); diff != "" {
   293  				t.Errorf("symlinks mismatch: got +, want -\n%s", diff)
   294  			}
   295  		})
   296  	}
   297  }