github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fstest/test_all/run.go (about) 1 // Run a test 2 3 // +build go1.11 4 5 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/build" 11 "io" 12 "log" 13 "os" 14 "os/exec" 15 "path" 16 "regexp" 17 "runtime" 18 "sort" 19 "strconv" 20 "strings" 21 "sync" 22 "time" 23 24 "github.com/rclone/rclone/fs" 25 "github.com/rclone/rclone/fstest/testserver" 26 ) 27 28 // Control concurrency per backend if required 29 var ( 30 oneOnlyMu sync.Mutex 31 oneOnly = map[string]*sync.Mutex{} 32 ) 33 34 // Run holds info about a running test 35 // 36 // A run just runs one command line, but it can be run multiple times 37 // if retries are needed. 38 type Run struct { 39 // Config 40 Remote string // name of the test remote 41 Backend string // name of the backend 42 Path string // path to the source directory 43 FastList bool // add -fast-list to tests 44 Short bool // add -short 45 NoRetries bool // don't retry if set 46 OneOnly bool // only run test for this backend at once 47 NoBinary bool // set to not build a binary 48 SizeLimit int64 // maximum test file size 49 Ignore map[string]struct{} 50 // Internals 51 cmdLine []string 52 cmdString string 53 try int 54 err error 55 output []byte 56 failedTests []string 57 runFlag string 58 logDir string // directory to place the logs 59 trialName string // name/log file name of current trial 60 trialNames []string // list of all the trials 61 } 62 63 // Runs records multiple Run objects 64 type Runs []*Run 65 66 // Sort interface 67 func (rs Runs) Len() int { return len(rs) } 68 func (rs Runs) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } 69 func (rs Runs) Less(i, j int) bool { 70 a, b := rs[i], rs[j] 71 if a.Backend < b.Backend { 72 return true 73 } else if a.Backend > b.Backend { 74 return false 75 } 76 if a.Remote < b.Remote { 77 return true 78 } else if a.Remote > b.Remote { 79 return false 80 } 81 if a.Path < b.Path { 82 return true 83 } else if a.Path > b.Path { 84 return false 85 } 86 if !a.FastList && b.FastList { 87 return true 88 } else if a.FastList && !b.FastList { 89 return false 90 } 91 return false 92 } 93 94 // dumpOutput prints the error output 95 func (r *Run) dumpOutput() { 96 log.Println("------------------------------------------------------------") 97 log.Printf("---- %q ----", r.cmdString) 98 log.Println(string(r.output)) 99 log.Println("------------------------------------------------------------") 100 } 101 102 // This converts a slice of test names into a regexp which matches 103 // them. 104 func testsToRegexp(tests []string) string { 105 var split []map[string]struct{} 106 // Make a slice with maps of the used parts at each level 107 for _, test := range tests { 108 for i, name := range strings.Split(test, "/") { 109 if i >= len(split) { 110 split = append(split, make(map[string]struct{})) 111 } 112 split[i][name] = struct{}{} 113 } 114 } 115 var out []string 116 for _, level := range split { 117 var testsInLevel = []string{} 118 for name := range level { 119 testsInLevel = append(testsInLevel, name) 120 } 121 sort.Strings(testsInLevel) 122 if len(testsInLevel) > 1 { 123 out = append(out, "^("+strings.Join(testsInLevel, "|")+")$") 124 } else { 125 out = append(out, "^"+testsInLevel[0]+"$") 126 } 127 } 128 return strings.Join(out, "/") 129 } 130 131 var failRe = regexp.MustCompile(`(?m)^\s*--- FAIL: (Test.*?) \(`) 132 133 // findFailures looks for all the tests which failed 134 func (r *Run) findFailures() { 135 oldFailedTests := r.failedTests 136 r.failedTests = nil 137 excludeParents := map[string]struct{}{} 138 ignored := 0 139 for _, matches := range failRe.FindAllSubmatch(r.output, -1) { 140 failedTest := string(matches[1]) 141 // Skip any ignored failures 142 if _, found := r.Ignore[failedTest]; found { 143 ignored++ 144 } else { 145 r.failedTests = append(r.failedTests, failedTest) 146 } 147 // Find all the parents of this test 148 parts := strings.Split(failedTest, "/") 149 for i := len(parts) - 1; i >= 1; i-- { 150 excludeParents[strings.Join(parts[:i], "/")] = struct{}{} 151 } 152 } 153 // Exclude the parents 154 var newTests = r.failedTests[:0] 155 for _, failedTest := range r.failedTests { 156 if _, excluded := excludeParents[failedTest]; !excluded { 157 newTests = append(newTests, failedTest) 158 } 159 } 160 r.failedTests = newTests 161 if len(r.failedTests) == 0 && ignored > 0 { 162 log.Printf("%q - Found %d ignored errors only - marking as good", r.cmdString, ignored) 163 r.err = nil 164 r.dumpOutput() 165 return 166 } 167 if len(r.failedTests) != 0 { 168 r.runFlag = testsToRegexp(r.failedTests) 169 } else { 170 r.runFlag = "" 171 } 172 if r.passed() && len(r.failedTests) != 0 { 173 log.Printf("%q - Expecting no errors but got: %v", r.cmdString, r.failedTests) 174 r.dumpOutput() 175 } else if !r.passed() && len(r.failedTests) == 0 { 176 log.Printf("%q - Expecting errors but got none: %v", r.cmdString, r.failedTests) 177 r.dumpOutput() 178 r.failedTests = oldFailedTests 179 } 180 } 181 182 // nextCmdLine returns the next command line 183 func (r *Run) nextCmdLine() []string { 184 cmdLine := r.cmdLine 185 if r.runFlag != "" { 186 cmdLine = append(cmdLine, "-test.run", r.runFlag) 187 } 188 return cmdLine 189 } 190 191 // trial runs a single test 192 func (r *Run) trial() { 193 cmdLine := r.nextCmdLine() 194 cmdString := toShell(cmdLine) 195 msg := fmt.Sprintf("%q - Starting (try %d/%d)", cmdString, r.try, *maxTries) 196 log.Println(msg) 197 logName := path.Join(r.logDir, r.trialName) 198 out, err := os.Create(logName) 199 if err != nil { 200 log.Fatalf("Couldn't create log file: %v", err) 201 } 202 defer func() { 203 err := out.Close() 204 if err != nil { 205 log.Fatalf("Failed to close log file: %v", err) 206 } 207 }() 208 _, _ = fmt.Fprintln(out, msg) 209 210 // Early exit if --try-run 211 if *dryRun { 212 log.Printf("Not executing as --dry-run: %v", cmdLine) 213 _, _ = fmt.Fprintln(out, "--dry-run is set - not running") 214 return 215 } 216 217 // Start the test server if required 218 finish, err := testserver.Start(r.Remote) 219 if err != nil { 220 log.Printf("%s: Failed to start test server: %v", r.Remote, err) 221 _, _ = fmt.Fprintf(out, "%s: Failed to start test server: %v\n", r.Remote, err) 222 r.err = err 223 return 224 } 225 defer finish() 226 227 // Internal buffer 228 var b bytes.Buffer 229 multiOut := io.MultiWriter(out, &b) 230 231 cmd := exec.Command(cmdLine[0], cmdLine[1:]...) 232 cmd.Stderr = multiOut 233 cmd.Stdout = multiOut 234 cmd.Dir = r.Path 235 start := time.Now() 236 r.err = cmd.Run() 237 r.output = b.Bytes() 238 duration := time.Since(start) 239 r.findFailures() 240 if r.passed() { 241 msg = fmt.Sprintf("%q - Finished OK in %v (try %d/%d)", cmdString, duration, r.try, *maxTries) 242 } else { 243 msg = fmt.Sprintf("%q - Finished ERROR in %v (try %d/%d): %v: Failed %v", cmdString, duration, r.try, *maxTries, r.err, r.failedTests) 244 } 245 log.Println(msg) 246 _, _ = fmt.Fprintln(out, msg) 247 } 248 249 // passed returns true if the test passed 250 func (r *Run) passed() bool { 251 return r.err == nil 252 } 253 254 // GOPATH returns the current GOPATH 255 func GOPATH() string { 256 gopath := os.Getenv("GOPATH") 257 if gopath == "" { 258 gopath = build.Default.GOPATH 259 } 260 return gopath 261 } 262 263 // BinaryName turns a package name into a binary name 264 func (r *Run) BinaryName() string { 265 binary := path.Base(r.Path) + ".test" 266 if runtime.GOOS == "windows" { 267 binary += ".exe" 268 } 269 return binary 270 } 271 272 // BinaryPath turns a package name into a binary path 273 func (r *Run) BinaryPath() string { 274 return path.Join(r.Path, r.BinaryName()) 275 } 276 277 // PackagePath returns the path to the package 278 func (r *Run) PackagePath() string { 279 return path.Join(GOPATH(), "src", r.Path) 280 } 281 282 // MakeTestBinary makes the binary we will run 283 func (r *Run) MakeTestBinary() { 284 binary := r.BinaryPath() 285 binaryName := r.BinaryName() 286 log.Printf("%s: Making test binary %q", r.Path, binaryName) 287 cmdLine := []string{"go", "test", "-c"} 288 if *dryRun { 289 log.Printf("Not executing: %v", cmdLine) 290 return 291 } 292 cmd := exec.Command(cmdLine[0], cmdLine[1:]...) 293 cmd.Dir = r.Path 294 err := cmd.Run() 295 if err != nil { 296 log.Fatalf("Failed to make test binary: %v", err) 297 } 298 if _, err := os.Stat(binary); err != nil { 299 log.Fatalf("Couldn't find test binary %q", binary) 300 } 301 } 302 303 // RemoveTestBinary removes the binary made in makeTestBinary 304 func (r *Run) RemoveTestBinary() { 305 if *dryRun { 306 return 307 } 308 binary := r.BinaryPath() 309 err := os.Remove(binary) // Delete the binary when finished 310 if err != nil { 311 log.Printf("Error removing test binary %q: %v", binary, err) 312 } 313 } 314 315 // Name returns the run name as a file name friendly string 316 func (r *Run) Name() string { 317 ns := []string{ 318 r.Backend, 319 strings.Replace(r.Path, "/", ".", -1), 320 r.Remote, 321 } 322 if r.FastList { 323 ns = append(ns, "fastlist") 324 } 325 ns = append(ns, fmt.Sprintf("%d", r.try)) 326 s := strings.Join(ns, "-") 327 s = strings.Replace(s, ":", "", -1) 328 return s 329 } 330 331 // Init the Run 332 func (r *Run) Init() { 333 prefix := "-test." 334 if r.NoBinary { 335 prefix = "-" 336 r.cmdLine = []string{"go", "test"} 337 } else { 338 r.cmdLine = []string{"./" + r.BinaryName()} 339 } 340 r.cmdLine = append(r.cmdLine, prefix+"v", prefix+"timeout", timeout.String(), "-remote", r.Remote) 341 r.try = 1 342 if *verbose { 343 r.cmdLine = append(r.cmdLine, "-verbose") 344 fs.Config.LogLevel = fs.LogLevelDebug 345 } 346 if *runOnly != "" { 347 r.cmdLine = append(r.cmdLine, prefix+"run", *runOnly) 348 } 349 if r.FastList { 350 r.cmdLine = append(r.cmdLine, "-fast-list") 351 } 352 if r.Short { 353 r.cmdLine = append(r.cmdLine, "-short") 354 } 355 if r.SizeLimit > 0 { 356 r.cmdLine = append(r.cmdLine, "-size-limit", strconv.FormatInt(r.SizeLimit, 10)) 357 } 358 r.cmdString = toShell(r.cmdLine) 359 } 360 361 // Logs returns all the log names 362 func (r *Run) Logs() []string { 363 return r.trialNames 364 } 365 366 // FailedTests returns the failed tests as a comma separated string, limiting the number 367 func (r *Run) FailedTests() string { 368 const maxTests = 5 369 ts := r.failedTests 370 if len(ts) > maxTests { 371 ts = ts[:maxTests:maxTests] 372 ts = append(ts, fmt.Sprintf("… (%d more)", len(r.failedTests)-maxTests)) 373 } 374 return strings.Join(ts, ", ") 375 } 376 377 // Run runs all the trials for this test 378 func (r *Run) Run(logDir string, result chan<- *Run) { 379 if r.OneOnly { 380 oneOnlyMu.Lock() 381 mu := oneOnly[r.Backend] 382 if mu == nil { 383 mu = new(sync.Mutex) 384 oneOnly[r.Backend] = mu 385 } 386 oneOnlyMu.Unlock() 387 mu.Lock() 388 defer mu.Unlock() 389 } 390 r.Init() 391 r.logDir = logDir 392 for r.try = 1; r.try <= *maxTries; r.try++ { 393 r.trialName = r.Name() + ".txt" 394 r.trialNames = append(r.trialNames, r.trialName) 395 log.Printf("Starting run with log %q", r.trialName) 396 r.trial() 397 if r.passed() || r.NoRetries { 398 break 399 } 400 } 401 if !r.passed() { 402 r.dumpOutput() 403 } 404 result <- r 405 }