@@ -6,9 +6,13 @@ package goproxytest
66
77import (
88 "fmt"
9+ "net/url"
910 "os"
1011 "path/filepath"
12+ "strings"
13+ "unicode"
1114
15+ "github.com/rogpeppe/go-internal/internal/goenv"
1216 "github.com/rogpeppe/go-internal/testscript"
1317)
1418
@@ -24,6 +28,13 @@ const GoModProxyDir = ".gomodproxy"
2428// It wraps p.Setup to start a proxy server when the directory is present,
2529// set GOPROXY and GONOSUMDB appropriately, and shuts down
2630// the server when the test completes.
31+ //
32+ // If a file exists in the top level of the [GoModProxyDir] directory
33+ // named "_enable_overlay", then instead of replacing the Go proxy entirely, the
34+ // existing Go proxy is used to serve modules that are not present in
35+ // the test script. In this case it uses the existing GOMODPROXY
36+ // download cache to avoid network downloads if it can, but takes care
37+ // not to pollute it with the localhost proxy server content.
2738func Setup (p * testscript.Params ) {
2839 origSetup := p .Setup
2940 p .Setup = func (env * testscript.Env ) error {
@@ -33,19 +44,84 @@ func Setup(p *testscript.Params) {
3344 }
3445 }
3546 proxyDir := filepath .Join (env .WorkDir , GoModProxyDir )
36- if info , err := os .Stat (proxyDir ); err == nil && info .IsDir () {
37- srv , err := NewServer (proxyDir , "" )
38- if err != nil {
39- return fmt .Errorf ("cannot start Go proxy: %v" , err )
47+ info , err := os .Stat (proxyDir )
48+ if err != nil || ! info .IsDir () {
49+ return nil
50+ }
51+ srv , err := NewServer (proxyDir , "" )
52+ if err != nil {
53+ return fmt .Errorf ("cannot start Go proxy: %v" , err )
54+ }
55+ env .Defer (srv .Close )
56+
57+ env .Vars = append (env .Vars , "GONOSUMDB=*" )
58+ if info , err := os .Stat (filepath .Join (proxyDir , "_enable_overlay" )); err == nil && info .Mode ().IsRegular () {
59+ // Overlay is enabled: overlay rather than replacing the proxy.
60+ // NOTE: if you change the list of names here, make sure
61+ // to keep ../internal/goenv/goenv.go:/^var goEnv
62+ // up to date.
63+ var goEnv struct {
64+ GOMODCACHE string
65+ GOPROXY string
66+ GONOPROXY string
4067 }
41- env .Defer (srv .Close )
42- // Add GOPROXY after calling the original setup
43- // so that it overrides any GOPROXY set there.
68+ if err := goenv .Unmarshal (& goEnv ); err != nil {
69+ return err
70+ }
71+ if goEnv .GOMODCACHE == "" || goEnv .GOPROXY == "" {
72+ // Shouldn't happen.
73+ return fmt .Errorf ("missing GOMODCACHE or GOPROXY from go env output" )
74+ }
75+ // Look for local test modules first, falling back to the
76+ // existing module download cache. See example under the
77+ // GOPROXY entry in https://go.dev/ref/mod#environment-variables
4478 env .Vars = append (env .Vars ,
45- "GOPROXY=" + srv .URL ,
46- "GONOSUMDB=*" ,
79+ fmt .Sprintf ("GOPROXY=%s,%s,%s" ,
80+ srv .URL ,
81+ uriFromPath (filepath .Join (goEnv .GOMODCACHE , "/cache/download" )),
82+ goEnv .GOPROXY ,
83+ ),
84+ // Pass through the following variables on the grounds that
85+ // they should be used when fetching external modules.
86+ // Hopefully they will be immaterial because tests shouldn't
87+ // be relying on modules that match them, but it seems safer
88+ // to do this.
89+ "GONOPROXY=" + goEnv .GONOPROXY ,
4790 )
91+ } else {
92+ env .Vars = append (env .Vars , "GOPROXY=" + srv .URL )
4893 }
4994 return nil
5095 }
5196}
97+
98+ // uriFromPath returns a file:// URI for the supplied file path.
99+ // This code was adapted from cuelang.org/go/internal/golangorgx/gopls/protocol.URIFromPath
100+ func uriFromPath (path string ) string {
101+ if path == "" {
102+ return ""
103+ }
104+ if ! isWindowsDrivePath (path ) {
105+ if abs , err := filepath .Abs (path ); err == nil {
106+ path = abs
107+ }
108+ }
109+ // Check the file path again, in case it became absolute.
110+ if isWindowsDrivePath (path ) {
111+ path = "/" + strings .ToUpper (string (path [0 ])) + path [1 :]
112+ }
113+ return (& url.URL {
114+ Scheme : "file" ,
115+ Path : filepath .ToSlash (path ),
116+ }).String ()
117+ }
118+
119+ // isWindowsDrivePath returns true if the file path is of the form used by
120+ // Windows. We check if the path begins with a drive letter, followed by a ":".
121+ // For example: C:/x/y/z.
122+ func isWindowsDrivePath (path string ) bool {
123+ if len (path ) < 3 {
124+ return false
125+ }
126+ return unicode .IsLetter (rune (path [0 ])) && path [1 ] == ':'
127+ }
0 commit comments