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 }