blob: d460259105ccc1a21fc76c1c2e60d473f9de8936 [file] [log] [blame]
// 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
}