github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/find_test.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "os/exec" 23 "regexp" 24 "runtime" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 // Tests match find function with all supported inputs on 31 // file pattern, size and time. 32 func TestMatchFind(t *testing.T) { 33 // List of various contexts used in each tests, 34 // tests are run in the same order as this list. 35 listFindContexts := []*findContext{ 36 { 37 clnt: &S3Client{ 38 targetURL: &ClientURL{}, 39 }, 40 ignorePattern: "*.go", 41 }, 42 { 43 clnt: &S3Client{ 44 targetURL: &ClientURL{}, 45 }, 46 namePattern: "console", 47 }, 48 { 49 clnt: &S3Client{ 50 targetURL: &ClientURL{}, 51 }, 52 pathPattern: "*console*", 53 }, 54 { 55 clnt: &S3Client{ 56 targetURL: &ClientURL{}, 57 }, 58 regexPattern: regexp.MustCompile(`^(\d+\.){3}\d+$`), 59 }, 60 { 61 clnt: &S3Client{ 62 targetURL: &ClientURL{}, 63 }, 64 olderThan: "1d", 65 }, 66 { 67 clnt: &S3Client{ 68 targetURL: &ClientURL{}, 69 }, 70 newerThan: "32000d", 71 }, 72 { 73 clnt: &S3Client{ 74 targetURL: &ClientURL{}, 75 }, 76 largerSize: 1024 * 1024, 77 }, 78 { 79 clnt: &S3Client{ 80 targetURL: &ClientURL{}, 81 }, 82 smallerSize: 1024, 83 }, 84 { 85 clnt: &S3Client{ 86 targetURL: &ClientURL{}, 87 }, 88 ignorePattern: "*.txt", 89 }, 90 { 91 clnt: &S3Client{ 92 targetURL: &ClientURL{}, 93 }, 94 }, 95 } 96 97 testCases := []struct { 98 content contentMessage 99 expectedMatch bool 100 }{ 101 // Matches ignore pattern, so match will be false - Test 1. 102 { 103 content: contentMessage{ 104 Key: "pkg/console/console.go", 105 }, 106 expectedMatch: false, 107 }, 108 // Matches name pattern - Test 2. 109 { 110 content: contentMessage{ 111 Key: "pkg/console/console.go", 112 }, 113 expectedMatch: true, 114 }, 115 // Matches path pattern - Test 3. 116 { 117 content: contentMessage{ 118 Key: "pkg/console/console.go", 119 }, 120 expectedMatch: true, 121 }, 122 // Matches regex pattern - Test 4. 123 { 124 content: contentMessage{ 125 Key: "192.168.1.1", 126 }, 127 expectedMatch: true, 128 }, 129 // Matches older than time - Test 5. 130 { 131 content: contentMessage{ 132 Time: time.Unix(11999, 0).UTC(), 133 }, 134 expectedMatch: true, 135 }, 136 // Matches newer than time - Test 6. 137 { 138 content: contentMessage{ 139 Time: time.Unix(12001, 0).UTC(), 140 }, 141 expectedMatch: true, 142 }, 143 // Matches size larger - Test 7. 144 { 145 content: contentMessage{ 146 Size: 1024 * 1024 * 2, 147 }, 148 expectedMatch: true, 149 }, 150 // Matches size smaller - Test 8. 151 { 152 content: contentMessage{ 153 Size: 1023, 154 }, 155 expectedMatch: true, 156 }, 157 // Does not match ignore pattern, so match will be true - Test 9. 158 { 159 content: contentMessage{ 160 Key: "pkg/console/console.go", 161 }, 162 expectedMatch: true, 163 }, 164 // No matching inputs were provided, so nothing to match return value is true - Test 10. 165 { 166 content: contentMessage{}, 167 expectedMatch: true, 168 }, 169 } 170 171 // Runs all the test cases and validate the expected conditions. 172 for i, testCase := range testCases { 173 gotMatch := matchFind(listFindContexts[i], testCase.content) 174 if testCase.expectedMatch != gotMatch { 175 t.Errorf("Test: %d, expected match %t, got %t", i+1, testCase.expectedMatch, gotMatch) 176 } 177 } 178 } 179 180 // Tests suffix strings trimmed off correctly at maxdepth. 181 func TestSuffixTrimmingAtMaxDepth(t *testing.T) { 182 testCases := []struct { 183 startPrefix string 184 path string 185 separator string 186 maxDepth uint 187 expectedNewPath string 188 }{ 189 // Tests at max depth 0. 190 { 191 startPrefix: "./", 192 path: ".git/refs/remotes", 193 separator: "/", 194 maxDepth: 0, 195 expectedNewPath: ".git/refs/remotes", 196 }, 197 // Tests at max depth 1. 198 { 199 startPrefix: "./", 200 path: ".git/refs/remotes", 201 separator: "/", 202 maxDepth: 1, 203 expectedNewPath: "./.git/", 204 }, 205 // Tests at max depth 2. 206 { 207 startPrefix: "./", 208 path: ".git/refs/remotes", 209 separator: "/", 210 maxDepth: 2, 211 expectedNewPath: "./.git/refs/", 212 }, 213 // Tests at max depth 3. 214 { 215 startPrefix: "./", 216 path: ".git/refs/remotes", 217 separator: "/", 218 maxDepth: 3, 219 expectedNewPath: "./.git/refs/remotes", 220 }, 221 // Tests with startPrefix empty. 222 { 223 startPrefix: "", 224 path: ".git/refs/remotes", 225 separator: "/", 226 maxDepth: 2, 227 expectedNewPath: ".git/refs/", 228 }, 229 // Tests with separator empty. 230 { 231 startPrefix: "", 232 path: ".git/refs/remotes", 233 separator: "", 234 maxDepth: 2, 235 expectedNewPath: ".g", 236 }, 237 // Tests with nested startPrefix paths - 1. 238 { 239 startPrefix: ".git/refs/", 240 path: ".git/refs/remotes", 241 separator: "/", 242 maxDepth: 1, 243 expectedNewPath: ".git/refs/remotes", 244 }, 245 // Tests with nested startPrefix paths - 2. 246 { 247 startPrefix: ".git/refs", 248 path: ".git/refs/remotes", 249 separator: "/", 250 maxDepth: 1, 251 expectedNewPath: ".git/refs/", 252 }, 253 } 254 255 // Run all the test cases and validate for returned new path. 256 for i, testCase := range testCases { 257 gotNewPath := trimSuffixAtMaxDepth(testCase.startPrefix, testCase.path, testCase.separator, testCase.maxDepth) 258 if testCase.expectedNewPath != gotNewPath { 259 t.Errorf("Test: %d, expected path %s, got %s", i+1, testCase.expectedNewPath, gotNewPath) 260 } 261 } 262 } 263 264 // Tests matching functions for name, path and regex. 265 func TestFindMatch(t *testing.T) { 266 // testFind is the structure used to contain params pertinent to find related tests 267 type testFind struct { 268 pattern, filePath, flagName string 269 match bool 270 } 271 272 basicTests := []testFind{ 273 // Name match tests - success cases. 274 {"*.jpg", "carter.jpg", "name", true}, 275 {"console", "pkg/console/console.go", "name", true}, 276 {"console.go", "pkg/console/console.go", "name", true}, 277 {"*XA==", "I/enjoy/morning/walks/XA==", "name ", true}, 278 {"*parser", "/This/might/mess up./the/parser", "name", true}, 279 {"*LTIxNDc0ODM2NDgvLTE=", "What/A/Naughty/String/LTIxNDc0ODM2NDgvLTE=", "name", true}, 280 {"*", "/bla/bla/bla/ ", "name", true}, 281 282 // Name match tests - failure cases. 283 {"*.jpg", "carter.jpeg", "name", false}, 284 {"*/test/*", "/test/bob/likes/cake", "name", false}, 285 {"*test/*", "bob/test/likes/cake", "name", false}, 286 {"*test/*", "bob/likes/test/cake", "name", false}, 287 {"*/test/*", "bob/likes/cake/test", "name", false}, 288 {"*.jpg", ".jpg/elves/are/evil", "name", false}, 289 { 290 "wq3YgNiB2ILYg9iE2IXYnNud3I/hoI7igIvigIzigI3igI7igI/igKrigKvigKzigK3igK7igaDi", 291 "An/Even/Bigger/String/wq3YgNiB2ILYg9iE2IXYnNud3I/hoI7igIvigIzigI3igI7igI/igKrigKvigKzigK3igK7igaDi", "name", false, 292 }, 293 {"๐ฟ๐๐", "well/this/isAN/odd/font/THE", "name", false}, 294 {"๐ฟ๐๐", "well/this/isAN/odd/font/The", "name", false}, 295 {"๐ฟ๐๐", "well/this/isAN/odd/font/๐ฃ๐ฑ๐ฎ", "name", false}, 296 {"๐ฟ๐๐", "what/a/strange/turn/of/events/๐ฃhe", "name", false}, 297 {"๐ฟ๐๐", "well/this/isAN/odd/font/๐ฟ๐๐", "name", true}, 298 299 // Path match tests - success cases. 300 {"*/test/*", "bob/test/likes/cake", "path", true}, 301 {"*/test/*", "/test/bob/likes/cake", "path", true}, 302 303 // Path match tests - failure cases. 304 {"*.jpg", ".jpg/elves/are/evil", "path", false}, 305 {"*/test/*", "test1/test2/test3/test", "path", false}, 306 {"*/ test /*", "test/test1/test2/test3/test", "path", false}, 307 {"*/test/*", " test /I/have/Really/Long/hair", "path", false}, 308 {"*XA==", "XA==/Height/is/a/social/construct", "path", false}, 309 {"*W", "/Word//this/is a/trickyTest", "path", false}, 310 {"LTIxNDc0ODM2NDgvLTE=", "LTIxNDc0ODM2NDgvLTE=/I/Am/One/Baaaaad/String", "path", false}, 311 {"/", "funky/path/name", "path", false}, 312 } 313 314 for _, test := range basicTests { 315 switch test.flagName { 316 case "name": 317 testMatch := nameMatch(test.pattern, test.filePath) 318 if testMatch != test.match { 319 t.Fatalf("Unexpected result %t, with pattern %s, flag %s and filepath %s \n", 320 !test.match, test.pattern, test.flagName, test.filePath) 321 } 322 case "path": 323 testMatch := pathMatch(test.pattern, test.filePath) 324 if testMatch != test.match { 325 t.Fatalf("Unexpected result %t, with pattern %s, flag %s and filepath %s \n", 326 !test.match, test.pattern, test.flagName, test.filePath) 327 } 328 } 329 } 330 } 331 332 // Tests string substitution function. 333 func TestStringReplace(t *testing.T) { 334 testCases := []struct { 335 str string 336 expectedStr string 337 content contentMessage 338 }{ 339 // Tests string replace {} without quotes. 340 { 341 str: "{}", 342 expectedStr: "path/1", 343 content: contentMessage{Key: "path/1"}, 344 }, 345 // Tests string replace {} with quotes. 346 { 347 str: `{""}`, 348 expectedStr: `"path/1"`, 349 content: contentMessage{Key: "path/1"}, 350 }, 351 // Tests string replace {base} 352 { 353 str: "{base}", 354 expectedStr: "1", 355 content: contentMessage{Key: "path/1"}, 356 }, 357 // Tests string replace {"base"} with quotes. 358 { 359 str: `{"base"}`, 360 expectedStr: `"1"`, 361 content: contentMessage{Key: "path/1"}, 362 }, 363 // Tests string replace {dir} 364 { 365 str: `{dir}`, 366 expectedStr: `path`, 367 content: contentMessage{Key: "path/1"}, 368 }, 369 // Tests string replace {"dir"} with quotes. 370 { 371 str: `{"dir"}`, 372 expectedStr: `"path"`, 373 content: contentMessage{Key: "path/1"}, 374 }, 375 // Tests string replace {"size"} with quotes. 376 { 377 str: `{"size"}`, 378 expectedStr: `"0 B"`, 379 content: contentMessage{Size: 0}, 380 }, 381 // Tests string replace {"time"} with quotes. 382 { 383 str: `{"time"}`, 384 expectedStr: `"2038-01-19 03:14:07 UTC"`, 385 content: contentMessage{ 386 Time: time.Unix(2147483647, 0).UTC(), 387 }, 388 }, 389 // Tests string replace {size} 390 { 391 str: `{size}`, 392 expectedStr: `1.0 MiB`, 393 content: contentMessage{Size: 1024 * 1024}, 394 }, 395 // Tests string replace {time} 396 { 397 str: `{time}`, 398 expectedStr: `2038-01-19 03:14:07 UTC`, 399 content: contentMessage{ 400 Time: time.Unix(2147483647, 0).UTC(), 401 }, 402 }, 403 } 404 for i, testCase := range testCases { 405 gotStr := stringsReplace(context.Background(), testCase.str, testCase.content) 406 if gotStr != testCase.expectedStr { 407 t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedStr, gotStr) 408 } 409 } 410 } 411 412 // Tests exit status, getExitStatus() function 413 func TestGetExitStatus(t *testing.T) { 414 if runtime.GOOS != "linux" { 415 t.Skip("Skipping on non-linux") 416 return 417 } 418 testCases := []struct { 419 command string 420 expectedExitStatus int 421 }{ 422 // Tests "No such file or directory", exit status code 2 423 { 424 command: "ls asdf", 425 expectedExitStatus: 2, 426 }, 427 { 428 command: "cp x x", 429 expectedExitStatus: 1, 430 }, 431 // expectedExitStatus for "command not found" case is 127, 432 // but exec command cannot capture anything since a process 433 // for the command could not be started at all, 434 // so the expectedExitStatus is 1 435 { 436 command: "asdf", 437 expectedExitStatus: 1, 438 }, 439 } 440 for i, testCase := range testCases { 441 commandArgs := strings.Split(testCase.command, " ") 442 cmd := exec.Command(commandArgs[0], commandArgs[1:]...) 443 // Return exit status of the command run 444 exitStatus := getExitStatus(cmd.Run()) 445 if exitStatus != testCase.expectedExitStatus { 446 t.Errorf("Test %d: Expected error status code for command \"%v\" is %v, got %v", 447 i+1, testCase.command, testCase.expectedExitStatus, exitStatus) 448 } 449 } 450 }