// 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 }