9698a2babb
Google cloud storage doesn't normally need retries, however certain things (eg bucket creation and removal) are rate limited and do generate 429 errors. Before this change the integration tests would regularly blow up with errors from GCS rate limiting bucket creation and removal. After this change we low level retry all operations using the same exponential backoff strategy as used in the google drive backend.
473 lines
11 KiB
Go
473 lines
11 KiB
Go
// Run tests for all the remotes. Run this with package names which
|
|
// need integration testing.
|
|
//
|
|
// See the `test` target in the Makefile.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"go/build"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/ncw/rclone/backend/all" // import all fs
|
|
"github.com/ncw/rclone/fs"
|
|
"github.com/ncw/rclone/fs/config"
|
|
"github.com/ncw/rclone/fs/list"
|
|
"github.com/ncw/rclone/fs/operations"
|
|
"github.com/ncw/rclone/fstest"
|
|
)
|
|
|
|
type remoteConfig struct {
|
|
Name string
|
|
SubDir bool
|
|
FastList bool
|
|
}
|
|
|
|
var (
|
|
remotes = []remoteConfig{
|
|
// {
|
|
// Name: "TestAmazonCloudDrive:",
|
|
// SubDir: false,
|
|
// FastList: false,
|
|
// },
|
|
{
|
|
Name: "TestB2:",
|
|
SubDir: true,
|
|
FastList: true,
|
|
},
|
|
{
|
|
Name: "TestCryptDrive:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestCryptSwift:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestDrive:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestDropbox:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestGoogleCloudStorage:",
|
|
SubDir: true,
|
|
FastList: true,
|
|
},
|
|
{
|
|
Name: "TestHubic:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestOneDrive:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestS3:",
|
|
SubDir: true,
|
|
FastList: true,
|
|
},
|
|
{
|
|
Name: "TestSftp:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestSwift:",
|
|
SubDir: true,
|
|
FastList: true,
|
|
},
|
|
{
|
|
Name: "TestYandex:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestFTP:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestBox:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestQingStor:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestAzureBlob:",
|
|
SubDir: true,
|
|
FastList: true,
|
|
},
|
|
{
|
|
Name: "TestPcloud:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestWebdav:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestCache:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
{
|
|
Name: "TestMega:",
|
|
SubDir: false,
|
|
FastList: false,
|
|
},
|
|
}
|
|
// Flags
|
|
maxTries = flag.Int("maxtries", 5, "Number of times to try each test")
|
|
runTests = flag.String("remotes", "", "Comma separated list of remotes to test, eg 'TestSwift:,TestS3'")
|
|
clean = flag.Bool("clean", false, "Instead of testing, clean all left over test directories")
|
|
runOnly = flag.String("run", "", "Run only those tests matching the regexp supplied")
|
|
timeout = flag.Duration("timeout", 30*time.Minute, "Maximum time to run each test for before giving up")
|
|
)
|
|
|
|
// test holds info about a running test
|
|
type test struct {
|
|
pkg string
|
|
remote string
|
|
subdir bool
|
|
cmdLine []string
|
|
cmdString string
|
|
try int
|
|
err error
|
|
output []byte
|
|
failedTests []string
|
|
runFlag string
|
|
}
|
|
|
|
// newTest creates a new test
|
|
func newTest(pkg, remote string, subdir bool, fastlist bool) *test {
|
|
binary := pkgBinary(pkg)
|
|
t := &test{
|
|
pkg: pkg,
|
|
remote: remote,
|
|
subdir: subdir,
|
|
cmdLine: []string{binary, "-test.timeout", (*timeout).String(), "-remote", remote},
|
|
try: 1,
|
|
}
|
|
if *fstest.Verbose {
|
|
t.cmdLine = append(t.cmdLine, "-test.v")
|
|
fs.Config.LogLevel = fs.LogLevelDebug
|
|
}
|
|
if *runOnly != "" {
|
|
t.cmdLine = append(t.cmdLine, "-test.run", *runOnly)
|
|
}
|
|
if subdir {
|
|
t.cmdLine = append(t.cmdLine, "-subdir")
|
|
}
|
|
if fastlist {
|
|
t.cmdLine = append(t.cmdLine, "-fast-list")
|
|
}
|
|
t.cmdString = toShell(t.cmdLine)
|
|
return t
|
|
}
|
|
|
|
// dumpOutput prints the error output
|
|
func (t *test) dumpOutput() {
|
|
log.Println("------------------------------------------------------------")
|
|
log.Printf("---- %q ----", t.cmdString)
|
|
log.Println(string(t.output))
|
|
log.Println("------------------------------------------------------------")
|
|
}
|
|
|
|
var failRe = regexp.MustCompile(`(?m)^--- FAIL: (Test\w*) \(`)
|
|
|
|
// findFailures looks for all the tests which failed
|
|
func (t *test) findFailures() {
|
|
oldFailedTests := t.failedTests
|
|
t.failedTests = nil
|
|
for _, matches := range failRe.FindAllSubmatch(t.output, -1) {
|
|
t.failedTests = append(t.failedTests, string(matches[1]))
|
|
}
|
|
if len(t.failedTests) != 0 {
|
|
t.runFlag = "^(" + strings.Join(t.failedTests, "|") + ")$"
|
|
} else {
|
|
t.runFlag = ""
|
|
}
|
|
if t.passed() && len(t.failedTests) != 0 {
|
|
log.Printf("%q - Expecting no errors but got: %v", t.cmdString, t.failedTests)
|
|
t.dumpOutput()
|
|
} else if !t.passed() && len(t.failedTests) == 0 {
|
|
log.Printf("%q - Expecting errors but got none: %v", t.cmdString, t.failedTests)
|
|
t.dumpOutput()
|
|
t.failedTests = oldFailedTests
|
|
}
|
|
}
|
|
|
|
// nextCmdLine returns the next command line
|
|
func (t *test) nextCmdLine() []string {
|
|
cmdLine := t.cmdLine[:]
|
|
if t.runFlag != "" {
|
|
cmdLine = append(cmdLine, "-test.run", t.runFlag)
|
|
}
|
|
return cmdLine
|
|
}
|
|
|
|
// if matches then is definitely OK in the shell
|
|
var shellOK = regexp.MustCompile("^[A-Za-z0-9./_:-]+$")
|
|
|
|
// converts a argv style input into a shell command
|
|
func toShell(args []string) (result string) {
|
|
for _, arg := range args {
|
|
if result != "" {
|
|
result += " "
|
|
}
|
|
if shellOK.MatchString(arg) {
|
|
result += arg
|
|
} else {
|
|
result += "'" + arg + "'"
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// trial runs a single test
|
|
func (t *test) trial() {
|
|
cmdLine := t.nextCmdLine()
|
|
cmdString := toShell(cmdLine)
|
|
log.Printf("%q - Starting (try %d/%d)", cmdString, t.try, *maxTries)
|
|
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
|
start := time.Now()
|
|
t.output, t.err = cmd.CombinedOutput()
|
|
duration := time.Since(start)
|
|
t.findFailures()
|
|
if t.passed() {
|
|
log.Printf("%q - Finished OK in %v (try %d/%d)", cmdString, duration, t.try, *maxTries)
|
|
} else {
|
|
log.Printf("%q - Finished ERROR in %v (try %d/%d): %v: Failed %v", cmdString, duration, t.try, *maxTries, t.err, t.failedTests)
|
|
}
|
|
}
|
|
|
|
// cleanFs runs a single clean fs for left over directories
|
|
func (t *test) cleanFs() error {
|
|
f, err := fs.NewFs(t.remote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entries, err := list.DirSorted(f, true, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return entries.ForDirError(func(dir fs.Directory) error {
|
|
remote := dir.Remote()
|
|
if fstest.MatchTestRemote.MatchString(remote) {
|
|
log.Printf("Purging %s%s", t.remote, remote)
|
|
dir, err := fs.NewFs(t.remote + remote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return operations.Purge(dir, "")
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// clean runs a single clean on a fs for left over directories
|
|
func (t *test) clean() {
|
|
log.Printf("%q - Starting clean (try %d/%d)", t.remote, t.try, *maxTries)
|
|
start := time.Now()
|
|
t.err = t.cleanFs()
|
|
if t.err != nil {
|
|
log.Printf("%q - Failed to purge %v", t.remote, t.err)
|
|
}
|
|
duration := time.Since(start)
|
|
if t.passed() {
|
|
log.Printf("%q - Finished OK in %v (try %d/%d)", t.cmdString, duration, t.try, *maxTries)
|
|
} else {
|
|
log.Printf("%q - Finished ERROR in %v (try %d/%d): %v", t.cmdString, duration, t.try, *maxTries, t.err)
|
|
}
|
|
}
|
|
|
|
// passed returns true if the test passed
|
|
func (t *test) passed() bool {
|
|
return t.err == nil
|
|
}
|
|
|
|
// run runs all the trials for this test
|
|
func (t *test) run(result chan<- *test) {
|
|
for t.try = 1; t.try <= *maxTries; t.try++ {
|
|
if *clean {
|
|
if !t.subdir {
|
|
t.clean()
|
|
}
|
|
} else {
|
|
t.trial()
|
|
}
|
|
if t.passed() {
|
|
break
|
|
}
|
|
}
|
|
if !t.passed() {
|
|
t.dumpOutput()
|
|
}
|
|
result <- t
|
|
}
|
|
|
|
// GOPATH returns the current GOPATH
|
|
func GOPATH() string {
|
|
gopath := os.Getenv("GOPATH")
|
|
if gopath == "" {
|
|
gopath = build.Default.GOPATH
|
|
}
|
|
return gopath
|
|
}
|
|
|
|
// turn a package name into a binary name
|
|
func pkgBinaryName(pkg string) string {
|
|
binary := path.Base(pkg) + ".test"
|
|
if runtime.GOOS == "windows" {
|
|
binary += ".exe"
|
|
}
|
|
return binary
|
|
}
|
|
|
|
// turn a package name into a binary path
|
|
func pkgBinary(pkg string) string {
|
|
return path.Join(pkgPath(pkg), pkgBinaryName(pkg))
|
|
}
|
|
|
|
// returns the path to the package
|
|
func pkgPath(pkg string) string {
|
|
return path.Join(GOPATH(), "src", pkg)
|
|
}
|
|
|
|
// cd into the package directory
|
|
func pkgChdir(pkg string) {
|
|
err := os.Chdir(pkgPath(pkg))
|
|
if err != nil {
|
|
log.Fatalf("Failed to chdir to package %q: %v", pkg, err)
|
|
}
|
|
}
|
|
|
|
// makeTestBinary makes the binary we will run
|
|
func makeTestBinary(pkg string) {
|
|
binaryName := pkgBinaryName(pkg)
|
|
log.Printf("%s: Making test binary %q", pkg, binaryName)
|
|
pkgChdir(pkg)
|
|
err := exec.Command("go", "test", "-c", "-o", binaryName).Run()
|
|
if err != nil {
|
|
log.Fatalf("Failed to make test binary: %v", err)
|
|
}
|
|
binary := pkgBinary(pkg)
|
|
if _, err := os.Stat(binary); err != nil {
|
|
log.Fatalf("Couldn't find test binary %q", binary)
|
|
}
|
|
}
|
|
|
|
// removeTestBinary removes the binary made in makeTestBinary
|
|
func removeTestBinary(pkg string) {
|
|
binary := pkgBinary(pkg)
|
|
err := os.Remove(binary) // Delete the binary when finished
|
|
if err != nil {
|
|
log.Printf("Error removing test binary %q: %v", binary, err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
packages := flag.Args()
|
|
log.Printf("Testing packages: %s", strings.Join(packages, ", "))
|
|
if *runTests != "" {
|
|
newRemotes := []remoteConfig{}
|
|
for _, name := range strings.Split(*runTests, ",") {
|
|
for i := range remotes {
|
|
if remotes[i].Name == name {
|
|
newRemotes = append(newRemotes, remotes[i])
|
|
goto found
|
|
}
|
|
}
|
|
log.Printf("Remote %q not found - inserting with default flags", name)
|
|
newRemotes = append(newRemotes, remoteConfig{Name: name})
|
|
found:
|
|
}
|
|
remotes = newRemotes
|
|
}
|
|
var names []string
|
|
for _, remote := range remotes {
|
|
names = append(names, remote.Name)
|
|
}
|
|
log.Printf("Testing remotes: %s", strings.Join(names, ", "))
|
|
|
|
start := time.Now()
|
|
if *clean {
|
|
config.LoadConfig()
|
|
packages = []string{"clean"}
|
|
} else {
|
|
for _, pkg := range packages {
|
|
makeTestBinary(pkg)
|
|
defer removeTestBinary(pkg)
|
|
}
|
|
}
|
|
|
|
// workaround for cache backend as we run simultaneous tests
|
|
_ = os.Setenv("RCLONE_CACHE_DB_WAIT_TIME", "30m")
|
|
|
|
// start the tests
|
|
results := make(chan *test, 8)
|
|
awaiting := 0
|
|
bools := []bool{false, true}
|
|
if *clean {
|
|
// Don't run -subdir and -fast-list if -clean
|
|
bools = bools[:1]
|
|
}
|
|
for _, pkg := range packages {
|
|
for _, remote := range remotes {
|
|
for _, subdir := range bools {
|
|
for _, fastlist := range bools {
|
|
if (!subdir || subdir && remote.SubDir) && (!fastlist || fastlist && remote.FastList) {
|
|
go newTest(pkg, remote.Name, subdir, fastlist).run(results)
|
|
awaiting++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for the tests to finish
|
|
var failed []*test
|
|
for ; awaiting > 0; awaiting-- {
|
|
t := <-results
|
|
if !t.passed() {
|
|
failed = append(failed, t)
|
|
}
|
|
}
|
|
duration := time.Since(start)
|
|
|
|
// Summarise results
|
|
log.Printf("SUMMARY")
|
|
if len(failed) == 0 {
|
|
log.Printf("PASS: All tests finished OK in %v", duration)
|
|
} else {
|
|
log.Printf("FAIL: %d tests failed in %v", len(failed), duration)
|
|
for _, t := range failed {
|
|
log.Printf(" * %s", toShell(t.nextCmdLine()))
|
|
log.Printf(" * Failed tests: %v", t.failedTests)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
}
|