// +build ignore

// Run tests for all the remotes
//
// Run with go run test_all.go
package main

import (
	"flag"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

var (
	remotes = []string{
		"TestSwift:",
		"TestS3:",
		"TestDrive:",
		"TestGoogleCloudStorage:",
		"TestDropbox:",
		"TestAmazonCloudDrive:",
		"TestOneDrive:",
		"TestHubic:",
		"TestB2:",
		"TestYandex:",
	}
	binary = "fs.test"
	// Flags
	maxTries = flag.Int("maxtries", 3, "Number of times to try each test")
	runTests = flag.String("run", "", "Comma separated list of remotes to test, eg 'TestSwift:,TestS3'")
	verbose  = flag.Bool("verbose", false, "Run the tests with -v")
)

// test holds info about a running test
type test struct {
	remote    string
	subdir    bool
	cmdLine   []string
	cmdString string
	try       int
	err       error
	output    []byte
}

// newTest creates a new test
func newTest(remote string, subdir bool) *test {
	t := &test{
		remote:  remote,
		subdir:  subdir,
		cmdLine: []string{"./" + binary, "-remote", remote},
		try:     1,
	}
	if *verbose {
		t.cmdLine = append(t.cmdLine, "-test.v")
	}
	if subdir {
		t.cmdLine = append(t.cmdLine, "-subdir")
	}
	t.cmdString = strings.Join(t.cmdLine, " ")
	return t
}

// trial runs a single test
func (t *test) trial() {
	log.Printf("%q - Starting (try %d/%d)", t.cmdString, t.try, *maxTries)
	cmd := exec.Command(t.cmdLine[0], t.cmdLine[1:]...)
	start := time.Now()
	t.output, t.err = cmd.CombinedOutput()
	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++ {
		t.trial()
		if t.passed() {
			break
		}
	}
	if !t.passed() {
		log.Println("------------------------------------------------------------")
		log.Println(string(t.output))
		log.Println("------------------------------------------------------------")
	}
	result <- t
}

// makeTestBinary makes the binary we will run
func makeTestBinary() {
	if runtime.GOOS == "windows" {
		binary += ".exe"
	}
	log.Printf("Making test binary %q", binary)
	err := exec.Command("go", "test", "-c", "-o", binary).Run()
	if err != nil {
		log.Fatalf("Failed to make test binary: %v", err)
	}
	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() {
	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()
	if *runTests != "" {
		remotes = strings.Split(*runTests, ",")
	}
	log.Printf("Testing remotes: %s", strings.Join(remotes, ", "))

	start := time.Now()
	makeTestBinary()
	defer removeTestBinary()

	// start the tests
	results := make(chan *test, 8)
	awaiting := 0
	for _, remote := range remotes {
		awaiting += 2
		go newTest(remote, false).run(results)
		go newTest(remote, true).run(results)
	}

	// 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
	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", t.cmdString)
		}
		os.Exit(1)
	}
}
