golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gitmirror/gitmirror_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"testing"
    17  
    18  	"golang.org/x/build/internal/envutil"
    19  	repospkg "golang.org/x/build/repos"
    20  )
    21  
    22  func TestHomepage(t *testing.T) {
    23  	tm := newTestMirror(t)
    24  	if body := tm.get("/"); !strings.Contains(body, "build") {
    25  		t.Errorf("expected body to contain \"build\", didn't: %q", body)
    26  	}
    27  }
    28  
    29  func TestDebugWatcher(t *testing.T) {
    30  	tm := newTestMirror(t)
    31  	tm.commit("hello world")
    32  	tm.loopOnce()
    33  
    34  	body := tm.get("/debug/watcher/build")
    35  	if substr := `watcher status for repo: "build"`; !strings.Contains(body, substr) {
    36  		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
    37  	}
    38  	if substr := "waiting"; !strings.Contains(body, substr) {
    39  		t.Fatalf("GET /debug/watcher/build: want %q in body, got %s", substr, body)
    40  	}
    41  }
    42  
    43  func TestArchive(t *testing.T) {
    44  	tm := newTestMirror(t)
    45  
    46  	// Start with a revision we know about.
    47  	tm.commit("hello world")
    48  	initialRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))
    49  	tm.loopOnce() // fetch the commit.
    50  	tm.get("/build.tar.gz?rev=" + initialRev)
    51  
    52  	// Now test one we don't see yet. It will be fetched automatically.
    53  	tm.commit("round two")
    54  	secondRev := strings.TrimSpace(tm.git(tm.gerrit, "rev-parse", "HEAD"))
    55  	// As of writing, the git version installed on the builders has some kind
    56  	// of bug that prevents the "git fetch" this triggers from working. Skip.
    57  	if strings.HasPrefix(tm.git(tm.gerrit, "version"), "git version 2.11") {
    58  		t.Skip("known-buggy git version")
    59  	}
    60  	tm.get("/build.tar.gz?rev=" + secondRev)
    61  
    62  	// Pick it up normally and re-fetch it to make sure we don't get confused.
    63  	tm.loopOnce()
    64  	tm.get("/build.tar.gz?rev=" + secondRev)
    65  }
    66  
    67  func TestMirror(t *testing.T) {
    68  	tm := newTestMirror(t)
    69  	for i := 0; i < 2; i++ {
    70  		tm.commit(fmt.Sprintf("revision %v", i))
    71  		rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
    72  		tm.loopOnce()
    73  		if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
    74  			t.Errorf("github HEAD is %v, want %v", githubRev, rev)
    75  		}
    76  		if csrRev := tm.git(tm.csr, "rev-parse", "HEAD"); rev != csrRev {
    77  			t.Errorf("csr HEAD is %v, want %v", csrRev, rev)
    78  		}
    79  	}
    80  }
    81  
    82  // Tests that mirroring an initially empty repository works. See golang/go#39597.
    83  // The repository still has to exist.
    84  func TestMirrorInitiallyEmpty(t *testing.T) {
    85  	tm := newTestMirror(t)
    86  	if err := tm.m.repos["build"].loopOnce(); err == nil {
    87  		t.Error("expected error mirroring empty repository, got none")
    88  	}
    89  	tm.commit("first commit")
    90  	tm.loopOnce()
    91  	rev := tm.git(tm.gerrit, "rev-parse", "HEAD")
    92  	if githubRev := tm.git(tm.github, "rev-parse", "HEAD"); rev != githubRev {
    93  		t.Errorf("github HEAD is %v, want %v", githubRev, rev)
    94  	}
    95  }
    96  
    97  type testMirror struct {
    98  	// Local paths to the copies of the build repo.
    99  	gerrit, github, csr string
   100  	m                   *gitMirror
   101  	server              *httptest.Server
   102  	buildRepo           *repo
   103  	t                   *testing.T
   104  }
   105  
   106  // newTestMirror returns a mirror configured to watch the "build" repository
   107  // and mirror it to GitHub and CSR. All repositories are faked out with local
   108  // versions created hermetically. The mirror is idle and must be pumped with
   109  // loopOnce.
   110  func newTestMirror(t *testing.T) *testMirror {
   111  	if _, err := exec.LookPath("git"); err != nil {
   112  		t.Skip("skipping; git not in PATH")
   113  	}
   114  
   115  	goBase := t.TempDir()
   116  	gerrit := filepath.Join(goBase, "build")
   117  	if err := os.Mkdir(gerrit, 0755); err != nil {
   118  		t.Fatalf("error creating gerrit build directory: %v", err)
   119  	}
   120  
   121  	tm := &testMirror{
   122  		gerrit: gerrit,
   123  		github: t.TempDir(),
   124  		csr:    t.TempDir(),
   125  		m: &gitMirror{
   126  			mux:      http.NewServeMux(),
   127  			cacheDir: t.TempDir(),
   128  			homeDir:  t.TempDir(),
   129  			// gitMirror generally expects goBase to be a URL, not
   130  			// a path, but git handles local paths just fine. As a
   131  			// result, gitMirror uses standard string concatenation
   132  			// rather than path.Join. Ensure the path ends in / to
   133  			// make sure concatenation is OK.
   134  			goBase:       goBase + "/",
   135  			repos:        map[string]*repo{},
   136  			mirrorGitHub: true,
   137  			mirrorCSR:    true,
   138  			timeoutScale: 0,
   139  		},
   140  		t: t,
   141  	}
   142  	tm.m.mux.HandleFunc("/", tm.m.handleRoot)
   143  	tm.server = httptest.NewServer(tm.m.mux)
   144  	t.Cleanup(tm.server.Close)
   145  
   146  	// The origin is non-bare so we can commit to it; the destinations are
   147  	// bare so we can push to them.
   148  	initRepo := func(dir string, bare bool) {
   149  		initArgs := []string{"init"}
   150  		if bare {
   151  			initArgs = append(initArgs, "--bare")
   152  		}
   153  		for _, args := range [][]string{
   154  			initArgs,
   155  			{"config", "user.name", "Gopher"},
   156  			{"config", "user.email", "gopher@golang.org"},
   157  		} {
   158  			cmd := exec.Command("git", args...)
   159  			envutil.SetDir(cmd, dir)
   160  			if out, err := cmd.CombinedOutput(); err != nil {
   161  				t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
   162  			}
   163  		}
   164  	}
   165  	initRepo(tm.gerrit, false)
   166  	initRepo(tm.github, true)
   167  	initRepo(tm.csr, true)
   168  
   169  	tm.buildRepo = tm.m.addRepo(&repospkg.Repo{
   170  		GoGerritProject:    "build",
   171  		ImportPath:         "golang.org/x/build",
   172  		MirrorToGitHub:     true,
   173  		GitHubRepo:         "golang/build",
   174  		MirrorToCSRProject: "golang-org",
   175  	})
   176  	if err := tm.buildRepo.init(); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	// Manually add mirror repos. We can't use tm.m.addMirrors, as they
   181  	// hard-codes the real remotes, but we need to use local test
   182  	// directories.
   183  	tm.buildRepo.addRemote("github", tm.github, "")
   184  	tm.buildRepo.addRemote("csr", tm.csr, "")
   185  
   186  	return tm
   187  }
   188  
   189  func (tm *testMirror) loopOnce() {
   190  	tm.t.Helper()
   191  	if err := tm.buildRepo.loopOnce(); err != nil {
   192  		tm.t.Fatal(err)
   193  	}
   194  }
   195  
   196  func (tm *testMirror) commit(content string) {
   197  	tm.t.Helper()
   198  	if err := os.WriteFile(filepath.Join(tm.gerrit, "README"), []byte(content), 0777); err != nil {
   199  		tm.t.Fatal(err)
   200  	}
   201  	tm.git(tm.gerrit, "add", ".")
   202  	tm.git(tm.gerrit, "commit", "-m", content)
   203  }
   204  
   205  func (tm *testMirror) git(dir string, args ...string) string {
   206  	tm.t.Helper()
   207  	cmd := exec.Command("git", args...)
   208  	envutil.SetDir(cmd, dir)
   209  	out, err := cmd.CombinedOutput()
   210  	if err != nil {
   211  		tm.t.Fatalf("git: %v, %s", err, out)
   212  	}
   213  	return string(out)
   214  }
   215  
   216  func (tm *testMirror) get(path string) string {
   217  	tm.t.Helper()
   218  	resp, err := http.Get(tm.server.URL + path)
   219  	if err != nil {
   220  		tm.t.Fatal(err)
   221  	}
   222  	body, err := io.ReadAll(resp.Body)
   223  	resp.Body.Close()
   224  	if err != nil {
   225  		tm.t.Fatal(err)
   226  	}
   227  	if resp.StatusCode != http.StatusOK {
   228  		tm.t.Fatalf("request for %q failed", path)
   229  	}
   230  	return string(body)
   231  }