diff options
Diffstat (limited to 'src/cmd/go/internal/vcweb/vcstest')
-rw-r--r-- | src/cmd/go/internal/vcweb/vcstest/vcstest.go | 169 | ||||
-rw-r--r-- | src/cmd/go/internal/vcweb/vcstest/vcstest_test.go | 170 |
2 files changed, 339 insertions, 0 deletions
diff --git a/src/cmd/go/internal/vcweb/vcstest/vcstest.go b/src/cmd/go/internal/vcweb/vcstest/vcstest.go new file mode 100644 index 0000000..d460259 --- /dev/null +++ b/src/cmd/go/internal/vcweb/vcstest/vcstest.go @@ -0,0 +1,169 @@ +// Copyright 2022 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 vcstest serves the repository scripts in cmd/go/testdata/vcstest +// using the [vcweb] script engine. +package vcstest + +import ( + "cmd/go/internal/vcs" + "cmd/go/internal/vcweb" + "cmd/go/internal/web" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "internal/testenv" + "io" + "log" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" +) + +var Hosts = []string{ + "vcs-test.golang.org", +} + +type Server struct { + vcweb *vcweb.Server + workDir string + HTTP *httptest.Server + HTTPS *httptest.Server +} + +// NewServer returns a new test-local vcweb server that serves VCS requests +// for modules with paths that begin with "vcs-test.golang.org" using the +// scripts in cmd/go/testdata/vcstest. +func NewServer() (srv *Server, err error) { + if vcs.VCSTestRepoURL != "" { + panic("vcs URL hooks already set") + } + + scriptDir := filepath.Join(testenv.GOROOT(nil), "src/cmd/go/testdata/vcstest") + + workDir, err := os.MkdirTemp("", "vcstest") + if err != nil { + return nil, err + } + defer func() { + if err != nil { + os.RemoveAll(workDir) + } + }() + + logger := log.Default() + if !testing.Verbose() { + logger = log.New(io.Discard, "", log.LstdFlags) + } + handler, err := vcweb.NewServer(scriptDir, workDir, logger) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + handler.Close() + } + }() + + srvHTTP := httptest.NewServer(handler) + httpURL, err := url.Parse(srvHTTP.URL) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + srvHTTP.Close() + } + }() + + srvHTTPS := httptest.NewTLSServer(handler) + httpsURL, err := url.Parse(srvHTTPS.URL) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + srvHTTPS.Close() + } + }() + + srv = &Server{ + vcweb: handler, + workDir: workDir, + HTTP: srvHTTP, + HTTPS: srvHTTPS, + } + vcs.VCSTestRepoURL = srv.HTTP.URL + vcs.VCSTestHosts = Hosts + + var interceptors []web.Interceptor + for _, host := range Hosts { + interceptors = append(interceptors, + web.Interceptor{Scheme: "http", FromHost: host, ToHost: httpURL.Host, Client: srv.HTTP.Client()}, + web.Interceptor{Scheme: "https", FromHost: host, ToHost: httpsURL.Host, Client: srv.HTTPS.Client()}) + } + web.EnableTestHooks(interceptors) + + fmt.Fprintln(os.Stderr, "vcs-test.golang.org rerouted to "+srv.HTTP.URL) + fmt.Fprintln(os.Stderr, "https://vcs-test.golang.org rerouted to "+srv.HTTPS.URL) + + return srv, nil +} + +func (srv *Server) Close() error { + if vcs.VCSTestRepoURL != srv.HTTP.URL { + panic("vcs URL hooks modified before Close") + } + vcs.VCSTestRepoURL = "" + vcs.VCSTestHosts = nil + web.DisableTestHooks() + + srv.HTTP.Close() + srv.HTTPS.Close() + err := srv.vcweb.Close() + if rmErr := os.RemoveAll(srv.workDir); err == nil { + err = rmErr + } + return err +} + +func (srv *Server) WriteCertificateFile() (string, error) { + b := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: srv.HTTPS.Certificate().Raw, + }) + + filename := filepath.Join(srv.workDir, "cert.pem") + if err := os.WriteFile(filename, b, 0644); err != nil { + return "", err + } + return filename, nil +} + +// TLSClient returns an http.Client that can talk to the httptest.Server +// whose certificate is written to the given file path. +func TLSClient(certFile string) (*http.Client, error) { + client := &http.Client{ + Transport: http.DefaultTransport.(*http.Transport).Clone(), + } + + pemBytes, err := os.ReadFile(certFile) + if err != nil { + return nil, err + } + + certpool := x509.NewCertPool() + if !certpool.AppendCertsFromPEM(pemBytes) { + return nil, fmt.Errorf("no certificates found in %s", certFile) + } + client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ + RootCAs: certpool, + } + + return client, nil +} diff --git a/src/cmd/go/internal/vcweb/vcstest/vcstest_test.go b/src/cmd/go/internal/vcweb/vcstest/vcstest_test.go new file mode 100644 index 0000000..4a6d600 --- /dev/null +++ b/src/cmd/go/internal/vcweb/vcstest/vcstest_test.go @@ -0,0 +1,170 @@ +// Copyright 2022 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 vcstest_test + +import ( + "cmd/go/internal/vcweb" + "errors" + "flag" + "fmt" + "io" + "io/fs" + "log" + "net" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" +) + +var ( + dir = flag.String("dir", "../../../testdata/vcstest", "directory containing scripts to serve") + host = flag.String("host", "localhost", "hostname on which to serve HTTP") + port = flag.Int("port", -1, "port on which to serve HTTP; if nonnegative, skips running tests") +) + +func TestMain(m *testing.M) { + flag.Parse() + + if *port >= 0 { + err := serveStandalone(*host, *port) + if err != nil { + log.Fatal(err) + } + os.Exit(0) + } + + m.Run() +} + +// serveStandalone serves the vcweb testdata in a standalone HTTP server. +func serveStandalone(host string, port int) (err error) { + scriptDir, err := filepath.Abs(*dir) + if err != nil { + return err + } + work, err := os.MkdirTemp("", "vcweb") + if err != nil { + return err + } + defer func() { + if rmErr := os.RemoveAll(work); err == nil { + err = rmErr + } + }() + + log.Printf("running scripts in %s", work) + + v, err := vcweb.NewServer(scriptDir, work, log.Default()) + if err != nil { + return err + } + + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return err + } + log.Printf("serving on http://%s:%d/", host, l.Addr().(*net.TCPAddr).Port) + + return http.Serve(l, v) +} + +// TestScripts verifies that the VCS setup scripts in cmd/go/testdata/vcstest +// run successfully. +func TestScripts(t *testing.T) { + scriptDir, err := filepath.Abs(*dir) + if err != nil { + t.Fatal(err) + } + s, err := vcweb.NewServer(scriptDir, t.TempDir(), log.Default()) + if err != nil { + t.Fatal(err) + } + srv := httptest.NewServer(s) + + // To check for data races in the handler, run the root handler to produce an + // overview of the script status at an arbitrary point during the test. + // (We ignore the output because the expected failure mode is a friendly stack + // dump from the race detector.) + t.Run("overview", func(t *testing.T) { + t.Parallel() + + time.Sleep(1 * time.Millisecond) // Give the other handlers time to race. + + resp, err := http.Get(srv.URL) + if err == nil { + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + } else { + t.Error(err) + } + }) + + t.Cleanup(func() { + // The subtests spawned by WalkDir run in parallel. When they complete, this + // Cleanup callback will run. At that point we fetch the root URL (which + // contains a status page), both to test that the root handler runs without + // crashing and to display a nice summary of the server's view of the test + // coverage. + resp, err := http.Get(srv.URL) + if err == nil { + var body []byte + body, err = io.ReadAll(resp.Body) + if err == nil && testing.Verbose() { + t.Logf("GET %s:\n%s", srv.URL, body) + } + resp.Body.Close() + } + if err != nil { + t.Error(err) + } + + srv.Close() + }) + + err = filepath.WalkDir(scriptDir, func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + + rel, err := filepath.Rel(scriptDir, path) + if err != nil { + return err + } + if rel == "README" { + return nil + } + + t.Run(filepath.ToSlash(rel), func(t *testing.T) { + t.Parallel() + + buf := new(strings.Builder) + logger := log.New(buf, "", log.LstdFlags) + // Load the script but don't try to serve the results: + // different VCS tools have different handler protocols, + // and the tests that actually use these repos will ensure + // that they are served correctly as a side effect anyway. + err := s.HandleScript(rel, logger, func(http.Handler) {}) + if buf.Len() > 0 { + t.Log(buf) + } + if err != nil { + if notInstalled := (vcweb.ServerNotInstalledError{}); errors.As(err, ¬Installed) || errors.Is(err, exec.ErrNotFound) { + t.Skip(err) + } + t.Error(err) + } + }) + return nil + }) + + if err != nil { + t.Error(err) + } +} |