github.com/mckael/restic@v0.8.3/run_integration_tests.go (about) 1 // +build ignore 2 3 package main 4 5 import ( 6 "bufio" 7 "bytes" 8 "encoding/base64" 9 "errors" 10 "flag" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "net/http" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "strings" 20 ) 21 22 // ForbiddenImports are the packages from the stdlib that should not be used in 23 // our code. 24 var ForbiddenImports = map[string]bool{ 25 "errors": true, 26 } 27 28 var runCrossCompile = flag.Bool("cross-compile", true, "run cross compilation tests") 29 30 func init() { 31 flag.Parse() 32 } 33 34 // CIEnvironment is implemented by environments where tests can be run. 35 type CIEnvironment interface { 36 Prepare() error 37 RunTests() error 38 Teardown() error 39 } 40 41 // TravisEnvironment is the environment in which Travis tests run. 42 type TravisEnvironment struct { 43 goxOSArch []string 44 env map[string]string 45 gcsCredentialsFile string 46 } 47 48 func (env *TravisEnvironment) getMinio() error { 49 tempfile, err := os.Create(filepath.Join(os.Getenv("GOPATH"), "bin", "minio")) 50 if err != nil { 51 return fmt.Errorf("create tempfile for minio download failed: %v", err) 52 } 53 54 url := fmt.Sprintf("https://dl.minio.io/server/minio/release/%s-%s/minio", 55 runtime.GOOS, runtime.GOARCH) 56 msg("downloading %v\n", url) 57 res, err := http.Get(url) 58 if err != nil { 59 return fmt.Errorf("error downloading minio server: %v", err) 60 } 61 62 _, err = io.Copy(tempfile, res.Body) 63 if err != nil { 64 return fmt.Errorf("error saving minio server to file: %v", err) 65 } 66 67 err = res.Body.Close() 68 if err != nil { 69 return fmt.Errorf("error closing HTTP download: %v", err) 70 } 71 72 err = tempfile.Close() 73 if err != nil { 74 msg("closing tempfile failed: %v\n", err) 75 return fmt.Errorf("error closing minio server file: %v", err) 76 } 77 78 err = os.Chmod(tempfile.Name(), 0755) 79 if err != nil { 80 return fmt.Errorf("chmod(minio-server) failed: %v", err) 81 } 82 83 msg("downloaded minio server to %v\n", tempfile.Name()) 84 return nil 85 } 86 87 // Prepare installs dependencies and starts services in order to run the tests. 88 func (env *TravisEnvironment) Prepare() error { 89 env.env = make(map[string]string) 90 91 msg("preparing environment for Travis CI\n") 92 93 pkgs := []string{ 94 "golang.org/x/tools/cmd/cover", 95 "github.com/pierrre/gotestcover", 96 "github.com/NebulousLabs/glyphcheck", 97 "github.com/golang/dep/cmd/dep", 98 "github.com/restic/rest-server/cmd/rest-server", 99 "github.com/restic/calens", 100 } 101 102 for _, pkg := range pkgs { 103 err := run("go", "get", pkg) 104 if err != nil { 105 return err 106 } 107 } 108 109 if err := env.getMinio(); err != nil { 110 return err 111 } 112 113 if *runCrossCompile { 114 // only test cross compilation on linux with Travis 115 if err := run("go", "get", "github.com/mitchellh/gox"); err != nil { 116 return err 117 } 118 if runtime.GOOS == "linux" { 119 env.goxOSArch = []string{ 120 "linux/386", "linux/amd64", 121 "windows/386", "windows/amd64", 122 "darwin/386", "darwin/amd64", 123 "freebsd/386", "freebsd/amd64", 124 "openbsd/386", "openbsd/amd64", 125 "linux/arm", "freebsd/arm", 126 } 127 } else { 128 env.goxOSArch = []string{runtime.GOOS + "/" + runtime.GOARCH} 129 } 130 131 msg("gox: OS/ARCH %v\n", env.goxOSArch) 132 } 133 134 // extract credentials file for GCS tests 135 if b64data := os.Getenv("RESTIC_TEST_GS_APPLICATION_CREDENTIALS_B64"); b64data != "" { 136 buf, err := base64.StdEncoding.DecodeString(b64data) 137 if err != nil { 138 return err 139 } 140 141 f, err := ioutil.TempFile("", "gcs-credentials-") 142 if err != nil { 143 return err 144 } 145 146 msg("saving GCS credentials to %v\n", f.Name()) 147 148 _, err = f.Write(buf) 149 if err != nil { 150 f.Close() 151 return err 152 } 153 154 env.gcsCredentialsFile = f.Name() 155 156 if err = f.Close(); err != nil { 157 return err 158 } 159 } 160 161 return nil 162 } 163 164 // Teardown stops backend services and cleans the environment again. 165 func (env *TravisEnvironment) Teardown() error { 166 msg("run travis teardown\n") 167 168 if env.gcsCredentialsFile != "" { 169 msg("remove gcs credentials file %v\n", env.gcsCredentialsFile) 170 return os.Remove(env.gcsCredentialsFile) 171 } 172 173 return nil 174 } 175 176 // RunTests starts the tests for Travis. 177 func (env *TravisEnvironment) RunTests() error { 178 // do not run fuse tests on darwin 179 if runtime.GOOS == "darwin" { 180 msg("skip fuse integration tests on %v\n", runtime.GOOS) 181 _ = os.Setenv("RESTIC_TEST_FUSE", "0") 182 } 183 184 env.env["GOPATH"] = os.Getenv("GOPATH") 185 if env.gcsCredentialsFile != "" { 186 env.env["RESTIC_TEST_GS_APPLICATION_CREDENTIALS"] = env.gcsCredentialsFile 187 } 188 189 // ensure that the following tests cannot be silently skipped on Travis 190 ensureTests := []string{ 191 "restic/backend/rest.TestBackendREST", 192 "restic/backend/sftp.TestBackendSFTP", 193 "restic/backend/s3.TestBackendMinio", 194 } 195 196 // if the test s3 repository is available, make sure that the test is not skipped 197 if os.Getenv("RESTIC_TEST_S3_REPOSITORY") != "" { 198 ensureTests = append(ensureTests, "restic/backend/s3.TestBackendS3") 199 } else { 200 msg("S3 repository not available\n") 201 } 202 203 // if the test swift service is available, make sure that the test is not skipped 204 if os.Getenv("RESTIC_TEST_SWIFT") != "" { 205 ensureTests = append(ensureTests, "restic/backend/swift.TestBackendSwift") 206 } else { 207 msg("Swift service not available\n") 208 } 209 210 // if the test b2 repository is available, make sure that the test is not skipped 211 if os.Getenv("RESTIC_TEST_B2_REPOSITORY") != "" { 212 ensureTests = append(ensureTests, "restic/backend/b2.TestBackendB2") 213 } else { 214 msg("B2 repository not available\n") 215 } 216 217 // if the test gs repository is available, make sure that the test is not skipped 218 if os.Getenv("RESTIC_TEST_GS_REPOSITORY") != "" { 219 ensureTests = append(ensureTests, "restic/backend/gs.TestBackendGS") 220 } else { 221 msg("GS repository not available\n") 222 } 223 224 env.env["RESTIC_TEST_DISALLOW_SKIP"] = strings.Join(ensureTests, ",") 225 226 if *runCrossCompile { 227 // compile for all target architectures with tags 228 for _, tags := range []string{"release", "debug"} { 229 err := runWithEnv(env.env, "gox", "-verbose", 230 "-osarch", strings.Join(env.goxOSArch, " "), 231 "-tags", tags, 232 "-output", "/tmp/{{.Dir}}_{{.OS}}_{{.Arch}}", 233 "./cmd/restic") 234 if err != nil { 235 return err 236 } 237 } 238 } 239 240 // run the build script 241 if err := run("go", "run", "build.go"); err != nil { 242 return err 243 } 244 245 // run the tests and gather coverage information 246 err := runWithEnv(env.env, "gotestcover", "-coverprofile", "all.cov", "github.com/restic/restic/cmd/...", "github.com/restic/restic/internal/...") 247 if err != nil { 248 return err 249 } 250 251 if err = runGofmt(); err != nil { 252 return err 253 } 254 255 if err = runDep(); err != nil { 256 return err 257 } 258 259 if err = runGlyphcheck(); err != nil { 260 return err 261 } 262 263 // check for forbidden imports 264 deps, err := env.findImports() 265 if err != nil { 266 return err 267 } 268 269 foundForbiddenImports := false 270 for name, imports := range deps { 271 for _, pkg := range imports { 272 if _, ok := ForbiddenImports[pkg]; ok { 273 fmt.Fprintf(os.Stderr, "========== package %v imports forbidden package %v\n", name, pkg) 274 foundForbiddenImports = true 275 } 276 } 277 } 278 279 if foundForbiddenImports { 280 return errors.New("CI: forbidden imports found") 281 } 282 283 // check that the man pages are up to date 284 manpath := filepath.Join("doc", "new-man") 285 if err := os.MkdirAll(manpath, 0755); err != nil { 286 return err 287 } 288 289 // check that the entries in changelog/ are valid 290 if err := run("calens"); err != nil { 291 fmt.Fprintf(os.Stderr, "calens failed, files in changelog/ are not valid\n") 292 } 293 294 return nil 295 } 296 297 // AppveyorEnvironment is the environment on Windows. 298 type AppveyorEnvironment struct{} 299 300 // Prepare installs dependencies and starts services in order to run the tests. 301 func (env *AppveyorEnvironment) Prepare() error { 302 msg("preparing environment for Appveyor CI\n") 303 return nil 304 } 305 306 // RunTests start the tests. 307 func (env *AppveyorEnvironment) RunTests() error { 308 return run("go", "run", "build.go", "-v", "-T") 309 } 310 311 // Teardown is a noop. 312 func (env *AppveyorEnvironment) Teardown() error { 313 return nil 314 } 315 316 // findGoFiles returns a list of go source code file names below dir. 317 func findGoFiles(dir string) (list []string, err error) { 318 err = filepath.Walk(dir, func(name string, fi os.FileInfo, err error) error { 319 relpath, err := filepath.Rel(dir, name) 320 if err != nil { 321 return err 322 } 323 324 if relpath == "vendor" || relpath == "pkg" { 325 return filepath.SkipDir 326 } 327 328 if filepath.Ext(relpath) == ".go" { 329 list = append(list, relpath) 330 } 331 332 return err 333 }) 334 335 return list, err 336 } 337 338 func msg(format string, args ...interface{}) { 339 fmt.Printf("CI: "+format, args...) 340 } 341 342 func updateEnv(env []string, override map[string]string) []string { 343 var newEnv []string 344 for _, s := range env { 345 d := strings.SplitN(s, "=", 2) 346 key := d[0] 347 348 if _, ok := override[key]; ok { 349 continue 350 } 351 352 newEnv = append(newEnv, s) 353 } 354 355 for k, v := range override { 356 newEnv = append(newEnv, k+"="+v) 357 } 358 359 return newEnv 360 } 361 362 func (env *TravisEnvironment) findImports() (map[string][]string, error) { 363 res := make(map[string][]string) 364 365 cmd := exec.Command("go", "list", "-f", `{{.ImportPath}} {{join .Imports " "}}`, "./internal/...", "./cmd/...") 366 cmd.Env = updateEnv(os.Environ(), env.env) 367 cmd.Stderr = os.Stderr 368 369 output, err := cmd.Output() 370 if err != nil { 371 return nil, err 372 } 373 374 sc := bufio.NewScanner(bytes.NewReader(output)) 375 for sc.Scan() { 376 wordScanner := bufio.NewScanner(strings.NewReader(sc.Text())) 377 wordScanner.Split(bufio.ScanWords) 378 379 if !wordScanner.Scan() { 380 return nil, fmt.Errorf("package name not found in line: %s", output) 381 } 382 name := wordScanner.Text() 383 var deps []string 384 385 for wordScanner.Scan() { 386 deps = append(deps, wordScanner.Text()) 387 } 388 389 res[name] = deps 390 } 391 392 return res, nil 393 } 394 395 func runGofmt() error { 396 dir, err := os.Getwd() 397 if err != nil { 398 return fmt.Errorf("Getwd(): %v", err) 399 } 400 401 files, err := findGoFiles(dir) 402 if err != nil { 403 return fmt.Errorf("error finding Go files: %v", err) 404 } 405 406 msg("runGofmt() with %d files\n", len(files)) 407 args := append([]string{"-l"}, files...) 408 cmd := exec.Command("gofmt", args...) 409 cmd.Stderr = os.Stderr 410 411 buf, err := cmd.Output() 412 if err != nil { 413 return fmt.Errorf("error running gofmt: %v\noutput: %s", err, buf) 414 } 415 416 if len(buf) > 0 { 417 return fmt.Errorf("not formatted with `gofmt`:\n%s", buf) 418 } 419 420 return nil 421 } 422 423 func runDep() error { 424 cmd := exec.Command("dep", "ensure", "-no-vendor", "-dry-run") 425 cmd.Stderr = os.Stderr 426 cmd.Stdout = os.Stdout 427 428 err := cmd.Run() 429 if err != nil { 430 return fmt.Errorf("error running dep: %v\nThis probably means that Gopkg.lock is not up to date, run 'dep ensure' and commit all changes", err) 431 } 432 433 return nil 434 } 435 436 func runGlyphcheck() error { 437 cmd := exec.Command("glyphcheck", "./cmd/...", "./internal/...") 438 cmd.Stderr = os.Stderr 439 440 buf, err := cmd.Output() 441 if err != nil { 442 return fmt.Errorf("error running glyphcheck: %v\noutput: %s", err, buf) 443 } 444 445 return nil 446 } 447 448 func run(command string, args ...string) error { 449 msg("run %v %v\n", command, strings.Join(args, " ")) 450 return runWithEnv(nil, command, args...) 451 } 452 453 // runWithEnv calls a command with the current environment, except the entries 454 // of the env map are set additionally. 455 func runWithEnv(env map[string]string, command string, args ...string) error { 456 msg("runWithEnv %v %v\n", command, strings.Join(args, " ")) 457 cmd := exec.Command(command, args...) 458 cmd.Stdout = os.Stdout 459 cmd.Stderr = os.Stderr 460 if env != nil { 461 cmd.Env = updateEnv(os.Environ(), env) 462 } 463 err := cmd.Run() 464 465 if err != nil { 466 return fmt.Errorf("error running %v %v: %v", 467 command, strings.Join(args, " "), err) 468 } 469 return nil 470 } 471 472 func isTravis() bool { 473 return os.Getenv("TRAVIS_BUILD_DIR") != "" 474 } 475 476 func isAppveyor() bool { 477 return runtime.GOOS == "windows" 478 } 479 480 func main() { 481 var env CIEnvironment 482 483 switch { 484 case isTravis(): 485 env = &TravisEnvironment{} 486 case isAppveyor(): 487 env = &AppveyorEnvironment{} 488 default: 489 fmt.Fprintln(os.Stderr, "unknown CI environment") 490 os.Exit(1) 491 } 492 493 err := env.Prepare() 494 if err != nil { 495 fmt.Fprintf(os.Stderr, "error preparing: %v\n", err) 496 os.Exit(1) 497 } 498 499 err = env.RunTests() 500 if err != nil { 501 fmt.Fprintf(os.Stderr, "error running tests: %v\n", err) 502 os.Exit(2) 503 } 504 505 err = env.Teardown() 506 if err != nil { 507 fmt.Fprintf(os.Stderr, "error during teardown: %v\n", err) 508 os.Exit(3) 509 } 510 }