package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/build/internal/envutil"
	repospkg "golang.org/x/build/repos"
)

func TestHomepage(t *testing.T) {
	tm := newTestMirror(t)
	if body := tm.get("/"); !strings.Contains(body, "build") {
		t.Errorf("expected body to contain \"build\", didn't: %q", body)
	}
}

func TestDebugWatcher(t *testing.T) {
	tm := newTestMirror(t)
	tm.commit("hello world")
	tm.loopOnce()

	body := tm.get("/debug/watcher/build")
	if substr := `watcher status for repo: "build"`; !strings.Contains(body, substr) {
		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
	}
	if substr := "waiting"; !strings.Contains(body, substr) {
		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
	}
}

func TestArchive(t *testing.T) {
	tm := newTestMirror(t)

	tm.commit("hello world")
	initialRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))
	tm.loopOnce() 
	tm.get("/build.tar.gz?rev=" + initialRev)

	tm.commit("round two")
	secondRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))

	if strings.HasPrefix(tm.git(tm.gerrit, "version"), "git version 2.11") {
		t.Skip("known-buggy git version")
	}
	tm.get("/build.tar.gz?rev=" + secondRev)

	tm.loopOnce()
	tm.get("/build.tar.gz?rev=" + secondRev)
}

func TestMirror(t *testing.T) {
	tm := newTestMirror(t)
	for i := 0; i < 2; i++ {
		tm.commit(fmt.Sprintf("revision %v", i))
		rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
		tm.loopOnce()
		if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
			t.Errorf("github HEAD is %v, want %v", githubRev, rev)
		}
		if csrRev := tm.git(tm.csr, "rev-parse", "HEAD"); rev != csrRev {
			t.Errorf("csr HEAD is %v, want %v", csrRev, rev)
		}
	}
}

func TestMirrorInitiallyEmpty(t *testing.T) {
	tm := newTestMirror(t)
	if err := tm.m.repos["build"].loopOnce(); err == nil {
		t.Error("expected error mirroring empty repository, got none")
	}
	tm.commit("first commit")
	tm.loopOnce()
	rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
	if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
		t.Errorf("github HEAD is %v, want %v", githubRev, rev)
	}
}

type testMirror struct {
	gerrit, github, csr string
	m                   *gitMirror
	server              *httptest.Server
	buildRepo           *repo
	t                   *testing.T
}

func newTestMirror(t *testing.T) *testMirror {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("skipping; git not in PATH")
	}

	tm := &testMirror{
		gerrit: t.TempDir(),
		github: t.TempDir(),
		csr:    t.TempDir(),
		m: &gitMirror{
			mux:          http.NewServeMux(),
			cacheDir:     t.TempDir(),
			homeDir:      t.TempDir(),
			repos:        map[string]*repo{},
			mirrorGitHub: true,
			mirrorCSR:    true,
			timeoutScale: 0,
		},
		t: t,
	}
	tm.m.mux.HandleFunc("/", tm.m.handleRoot)
	tm.server = httptest.NewServer(tm.m.mux)
	t.Cleanup(tm.server.Close)

	gitConfig := &bytes.Buffer{}
	overrideRepo := func(fromURL, toDir string, bare bool) {
		initArgs := []string{"init"}
		if bare {
			initArgs = append(initArgs, "--bare")
		}
		for _, args := range [][]string{
			initArgs,
			{"config", "user.name", "Gopher"},
			{"config", "user.email", "gopher@golang.org"},
		} {
			cmd := exec.Command("git", args...)
			envutil.SetDir(cmd, toDir)
			if out, err := cmd.CombinedOutput(); err != nil {
				t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
			}
		}

		fmt.Fprintf(gitConfig, "[url %q]\n  insteadOf = %v\n", toDir, fromURL)
	}
	overrideRepo("https:
	overrideRepo("git@github.com:golang/build.git", tm.github, true)
	overrideRepo("https:

	if err := os.MkdirAll(filepath.Join(tm.m.homeDir, ".config/git"), 0777); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(filepath.Join(tm.m.homeDir, ".config/git/config"), gitConfig.Bytes(), 0777); err != nil {
		t.Fatal(err)
	}
	tm.buildRepo = tm.m.addRepo(&repospkg.Repo{
		GoGerritProject:    "build",
		ImportPath:         "golang.org/x/build",
		MirrorToGitHub:     true,
		GitHubRepo:         "golang/build",
		MirrorToCSRProject: "golang-org",
	})
	if err := tm.buildRepo.init(); err != nil {
		t.Fatal(err)
	}
	if err := tm.m.addMirrors(); err != nil {
		t.Fatal(err)
	}
	return tm
}

func (tm *testMirror) loopOnce() {
	tm.t.Helper()
	if err := tm.buildRepo.loopOnce(); err != nil {
		tm.t.Fatal(err)
	}
}

func (tm *testMirror) commit(content string) {
	tm.t.Helper()
	if err := ioutil.WriteFile(filepath.Join(tm.gerrit, "README"), []byte(content), 0777); err != nil {
		tm.t.Fatal(err)
	}
	tm.git(tm.gerrit, "add", ".")
	tm.git(tm.gerrit, "commit", "-m", content)
}

func (tm *testMirror) git(dir string, args ...string) string {
	tm.t.Helper()
	cmd := exec.Command("git", args...)
	envutil.SetDir(cmd, dir)
	out, err := cmd.CombinedOutput()
	if err != nil {
		tm.t.Fatalf("git: %v, %s", err, out)
	}
	return string(out)
}

func (tm *testMirror) get(path string) string {
	tm.t.Helper()
	resp, err := http.Get(tm.server.URL + path)
	if err != nil {
		tm.t.Fatal(err)
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		tm.t.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		tm.t.Fatalf("request for %q failed", path)
	}
	return string(body)
}
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/build/internal/envutil"
	repospkg "golang.org/x/build/repos"
)

func TestHomepage(t *testing.T) {
	tm := newTestMirror(t)
	if body := tm.get("/"); !strings.Contains(body, "build") {
		t.Errorf("expected body to contain \"build\", didn't: %q", body)
	}
}

func TestDebugWatcher(t *testing.T) {
	tm := newTestMirror(t)
	tm.commit("hello world")
	tm.loopOnce()

	body := tm.get("/debug/watcher/build")
	if substr := `watcher status for repo: "build"`; !strings.Contains(body, substr) {
		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
	}
	if substr := "waiting"; !strings.Contains(body, substr) {
		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
	}
}

func TestArchive(t *testing.T) {
	tm := newTestMirror(t)

	// Start with a revision we know about.
	tm.commit("hello world")
	initialRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))
	tm.loopOnce() // fetch the commit.
	tm.get("/build.tar.gz?rev=" + initialRev)

	// Now test one we don't see yet. It will be fetched automatically.
	tm.commit("round two")
	secondRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))
	// As of writing, the git version installed on the builders has some kind
	// of bug that prevents the "git fetch" this triggers from working. Skip.
	if strings.HasPrefix(tm.git(tm.gerrit, "version"), "git version 2.11") {
		t.Skip("known-buggy git version")
	}
	tm.get("/build.tar.gz?rev=" + secondRev)

	// Pick it up normally and re-fetch it to make sure we don't get confused.
	tm.loopOnce()
	tm.get("/build.tar.gz?rev=" + secondRev)
}

func TestMirror(t *testing.T) {
	tm := newTestMirror(t)
	for i := 0; i < 2; i++ {
		tm.commit(fmt.Sprintf("revision %v", i))
		rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
		tm.loopOnce()
		if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
			t.Errorf("github HEAD is %v, want %v", githubRev, rev)
		}
		if csrRev := tm.git(tm.csr, "rev-parse", "HEAD"); rev != csrRev {
			t.Errorf("csr HEAD is %v, want %v", csrRev, rev)
		}
	}
}

// Tests that mirroring an initially empty repository works. See golang/go#39597.
// The repository still has to exist.
func TestMirrorInitiallyEmpty(t *testing.T) {
	tm := newTestMirror(t)
	if err := tm.m.repos["build"].loopOnce(); err == nil {
		t.Error("expected error mirroring empty repository, got none")
	}
	tm.commit("first commit")
	tm.loopOnce()
	rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
	if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
		t.Errorf("github HEAD is %v, want %v", githubRev, rev)
	}
}

type testMirror struct {
	gerrit, github, csr string
	m                   *gitMirror
	server              *httptest.Server
	buildRepo           *repo
	t                   *testing.T
}

// newTestMirror returns a mirror configured to watch the "build" repository
// and mirror it to GitHub and CSR. All repositories are faked out with local
// versions created hermetically. The mirror is idle and must be pumped with
// loopOnce.
func newTestMirror(t *testing.T) *testMirror {
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("skipping; git not in PATH")
	}

	tm := &testMirror{
		gerrit: t.TempDir(),
		github: t.TempDir(),
		csr:    t.TempDir(),
		m: &gitMirror{
			mux:          http.NewServeMux(),
			cacheDir:     t.TempDir(),
			homeDir:      t.TempDir(),
			repos:        map[string]*repo{},
			mirrorGitHub: true,
			mirrorCSR:    true,
			timeoutScale: 0,
		},
		t: t,
	}
	tm.m.mux.HandleFunc("/", tm.m.handleRoot)
	tm.server = httptest.NewServer(tm.m.mux)
	t.Cleanup(tm.server.Close)

	// Write a git config with each of the relevant repositories replaced by a
	// local version. The origin is non-bare so we can commit to it; the
	// destinations are bare so we can push to them.
	gitConfig := &bytes.Buffer{}
	overrideRepo := func(fromURL, toDir string, bare bool) {
		initArgs := []string{"init"}
		if bare {
			initArgs = append(initArgs, "--bare")
		}
		for _, args := range [][]string{
			initArgs,
			{"config", "user.name", "Gopher"},
			{"config", "user.email", "gopher@golang.org"},
		} {
			cmd := exec.Command("git", args...)
			envutil.SetDir(cmd, toDir)
			if out, err := cmd.CombinedOutput(); err != nil {
				t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
			}
		}

		fmt.Fprintf(gitConfig, "[url %q]\n  insteadOf = %v\n", toDir, fromURL)
	}
	overrideRepo("https://go.googlesource.com/build", tm.gerrit, false)
	overrideRepo("git@github.com:golang/build.git", tm.github, true)
	overrideRepo("https://source.developers.google.com/p/golang-org/r/build", tm.csr, true)

	if err := os.MkdirAll(filepath.Join(tm.m.homeDir, ".config/git"), 0777); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(filepath.Join(tm.m.homeDir, ".config/git/config"), gitConfig.Bytes(), 0777); err != nil {
		t.Fatal(err)
	}
	tm.buildRepo = tm.m.addRepo(&repospkg.Repo{
		GoGerritProject:    "build",
		ImportPath:         "golang.org/x/build",
		MirrorToGitHub:     true,
		GitHubRepo:         "golang/build",
		MirrorToCSRProject: "golang-org",
	})
	if err := tm.buildRepo.init(); err != nil {
		t.Fatal(err)
	}
	if err := tm.m.addMirrors(); err != nil {
		t.Fatal(err)
	}
	return tm
}

func (tm *testMirror) loopOnce() {
	tm.t.Helper()
	if err := tm.buildRepo.loopOnce(); err != nil {
		tm.t.Fatal(err)
	}
}

func (tm *testMirror) commit(content string) {
	tm.t.Helper()
	if err := ioutil.WriteFile(filepath.Join(tm.gerrit, "README"), []byte(content), 0777); err != nil {
		tm.t.Fatal(err)
	}
	tm.git(tm.gerrit, "add", ".")
	tm.git(tm.gerrit, "commit", "-m", content)
}

func (tm *testMirror) git(dir string, args ...string) string {
	tm.t.Helper()
	cmd := exec.Command("git", args...)
	envutil.SetDir(cmd, dir)
	out, err := cmd.CombinedOutput()
	if err != nil {
		tm.t.Fatalf("git: %v, %s", err, out)
	}
	return string(out)
}

func (tm *testMirror) get(path string) string {
	tm.t.Helper()
	resp, err := http.Get(tm.server.URL + path)
	if err != nil {
		tm.t.Fatal(err)
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		tm.t.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		tm.t.Fatalf("request for %q failed", path)
	}
	return string(body)
}
