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