cmd/pkgsite: refactor and add test

Create all the getters in one place so we can test that.

Change-Id: I748a823562ee6e247ea9eece8e77d65bc68d68d4
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/386195
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/cmd/pkgsite/main.go b/cmd/pkgsite/main.go
index 43e2786..0ec7626 100644
--- a/cmd/pkgsite/main.go
+++ b/cmd/pkgsite/main.go
@@ -134,7 +134,11 @@
 		}
 	}
 
-	server, err := newServer(ctx, paths, *gopathMode, modCacheDir, cacheMods, prox)
+	getters, err := buildGetters(ctx, paths, *gopathMode, modCacheDir, cacheMods, prox)
+	if err != nil {
+		die("%s", err)
+	}
+	server, err := newServer(getters, prox)
 	if err != nil {
 		die("%s", err)
 	}
@@ -145,12 +149,6 @@
 	die("%v", http.ListenAndServe(*httpAddr, mw(router)))
 }
 
-func die(format string, args ...interface{}) {
-	fmt.Fprintf(os.Stderr, format, args...)
-	fmt.Fprintln(os.Stderr)
-	os.Exit(1)
-}
-
 func collectPaths(args []string) []string {
 	var paths []string
 	for _, arg := range args {
@@ -159,8 +157,8 @@
 	return paths
 }
 
-func newServer(ctx context.Context, paths []string, gopathMode bool, downloadDir string, cacheMods []internal.Modver, prox *proxy.Client) (*frontend.Server, error) {
-	getters := buildGetters(ctx, paths, gopathMode)
+func buildGetters(ctx context.Context, paths []string, gopathMode bool, downloadDir string, cacheMods []internal.Modver, prox *proxy.Client) ([]fetch.ModuleGetter, error) {
+	getters := buildPathGetters(ctx, paths, gopathMode)
 	if downloadDir != "" {
 		g, err := fetch.NewFSProxyModuleGetter(downloadDir, cacheMods)
 		if err != nil {
@@ -171,30 +169,10 @@
 	if prox != nil {
 		getters = append(getters, fetch.NewProxyModuleGetter(prox, source.NewClient(time.Second)))
 	}
-	lds := fetchdatasource.Options{
-		Getters:              getters,
-		ProxyClientForLatest: prox,
-		BypassLicenseCheck:   true,
-	}.New()
-	server, err := frontend.NewServer(frontend.ServerConfig{
-		DataSourceGetter: func(context.Context) internal.DataSource { return lds },
-		TemplateFS:       template.TrustedFSFromEmbed(static.FS),
-		StaticFS:         static.FS,
-		ThirdPartyFS:     thirdparty.FS,
-	})
-	if err != nil {
-		return nil, err
-	}
-	for _, g := range getters {
-		p, fsys := g.SourceFS()
-		if p != "" {
-			server.InstallFS(p, fsys)
-		}
-	}
-	return server, nil
+	return getters, nil
 }
 
-func buildGetters(ctx context.Context, paths []string, gopathMode bool) []fetch.ModuleGetter {
+func buildPathGetters(ctx context.Context, paths []string, gopathMode bool) []fetch.ModuleGetter {
 	var getters []fetch.ModuleGetter
 	loaded := len(paths)
 	for _, path := range paths {
@@ -221,6 +199,30 @@
 	return getters
 }
 
+func newServer(getters []fetch.ModuleGetter, prox *proxy.Client) (*frontend.Server, error) {
+	lds := fetchdatasource.Options{
+		Getters:              getters,
+		ProxyClientForLatest: prox,
+		BypassLicenseCheck:   true,
+	}.New()
+	server, err := frontend.NewServer(frontend.ServerConfig{
+		DataSourceGetter: func(context.Context) internal.DataSource { return lds },
+		TemplateFS:       template.TrustedFSFromEmbed(static.FS),
+		StaticFS:         static.FS,
+		ThirdPartyFS:     thirdparty.FS,
+	})
+	if err != nil {
+		return nil, err
+	}
+	for _, g := range getters {
+		p, fsys := g.SourceFS()
+		if p != "" {
+			server.InstallFS(p, fsys)
+		}
+	}
+	return server, nil
+}
+
 func defaultCacheDir() (string, error) {
 	out, err := runGo("", "env", "GOMODCACHE")
 	if err != nil {
@@ -282,3 +284,9 @@
 	}
 	return outPaths, cacheMods, nil
 }
+
+func die(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	fmt.Fprintln(os.Stderr)
+	os.Exit(1)
+}
diff --git a/cmd/pkgsite/main_test.go b/cmd/pkgsite/main_test.go
index 86d5cab..a0dfac3 100644
--- a/cmd/pkgsite/main_test.go
+++ b/cmd/pkgsite/main_test.go
@@ -17,6 +17,7 @@
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/net/html"
 	"golang.org/x/pkgsite/internal"
+	"golang.org/x/pkgsite/internal/proxy"
 	"golang.org/x/pkgsite/internal/proxy/proxytest"
 	"golang.org/x/pkgsite/internal/testing/htmlcheck"
 )
@@ -32,7 +33,82 @@
 	}
 )
 
-func Test(t *testing.T) {
+func TestBuildGetters(t *testing.T) {
+	repoPath := func(fn string) string { return filepath.Join("..", "..", fn) }
+
+	abs := func(dir string) string {
+		a, err := filepath.Abs(dir)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return a
+	}
+
+	ctx := context.Background()
+	localModule := repoPath("internal/fetch/testdata/has_go_mod")
+	cacheDir := repoPath("internal/fetch/testdata/modcache")
+	testModules := proxytest.LoadTestModules(repoPath("internal/proxy/testdata"))
+	prox, teardown := proxytest.SetupTestClient(t, testModules)
+	defer teardown()
+
+	localGetter := "Dir(example.com/testmod, " + abs(localModule) + ")"
+	cacheGetter := "FSProxy(" + abs(cacheDir) + ")"
+	for _, test := range []struct {
+		name     string
+		paths    []string
+		cmods    []internal.Modver
+		cacheDir string
+		prox     *proxy.Client
+		want     []string
+	}{
+		{
+			name:  "local only",
+			paths: []string{localModule},
+			want:  []string{localGetter},
+		},
+		{
+			name:     "cache",
+			cacheDir: cacheDir,
+			want:     []string{cacheGetter},
+		},
+		{
+			name: "proxy",
+			prox: prox,
+			want: []string{"Proxy"},
+		},
+		{
+			name:     "all three",
+			paths:    []string{localModule},
+			cacheDir: cacheDir,
+			prox:     prox,
+			want:     []string{localGetter, cacheGetter, "Proxy"},
+		},
+		{
+			name:     "list",
+			paths:    []string{localModule},
+			cacheDir: cacheDir,
+			cmods:    []internal.Modver{{Path: "foo", Version: "v1.2.3"}},
+			want:     []string{localGetter, "FSProxy(" + abs(cacheDir) + ", foo@v1.2.3)"},
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			getters, err := buildGetters(ctx, test.paths, false, test.cacheDir, test.cmods, test.prox)
+			if err != nil {
+				t.Fatal(err)
+			}
+			var got []string
+			for _, g := range getters {
+				got = append(got, g.String())
+			}
+
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("mismatch (-want, +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestServer(t *testing.T) {
 	repoPath := func(fn string) string { return filepath.Join("..", "..", fn) }
 
 	abs := func(dir string) string {
@@ -49,7 +125,8 @@
 	prox, teardown := proxytest.SetupTestClient(t, testModules)
 	defer teardown()
 
-	server, err := newServer(context.Background(), []string{localModule}, false, cacheDir, nil, prox)
+	getters, err := buildGetters(context.Background(), []string{localModule}, false, cacheDir, nil, prox)
+	server, err := newServer(getters, prox)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/fetch/getters.go b/internal/fetch/getters.go
index 4fb74ef..8760747 100644
--- a/internal/fetch/getters.go
+++ b/internal/fetch/getters.go
@@ -18,6 +18,7 @@
 	"os"
 	"path"
 	"path/filepath"
+	"sort"
 	"strings"
 	"time"
 
@@ -54,6 +55,9 @@
 	// returned values are intended to be passed to
 	// internal/frontend.Server.InstallFiles.
 	SourceFS() (string, fs.FS)
+
+	// String returns a representation of the getter for testing and debugging.
+	String() string
 }
 
 type proxyModuleGetter struct {
@@ -96,6 +100,10 @@
 	return "", nil
 }
 
+func (g *proxyModuleGetter) String() string {
+	return "Proxy"
+}
+
 // Version and commit time are pre specified when fetching a local module, as these
 // fields are normally obtained from a proxy.
 var (
@@ -190,6 +198,11 @@
 	return path.Join(filepath.ToSlash(g.dir), g.modulePath)
 }
 
+// For testing.
+func (g *directoryModuleGetter) String() string {
+	return fmt.Sprintf("Dir(%s, %s)", g.modulePath, g.dir)
+}
+
 // An fsProxyModuleGetter gets modules from a directory in the filesystem that
 // is organized like the module cache, with a cache/download directory that has
 // paths that correspond to proxy URLs. An example of such a directory is
@@ -384,3 +397,16 @@
 	}
 	return filepath.Join(g.dir, "cache", "download", filepath.FromSlash(ep), "@v"), nil
 }
+
+// For testing.
+func (g *fsProxyModuleGetter) String() string {
+	if g.allowed == nil {
+		return fmt.Sprintf("FSProxy(%s)", g.dir)
+	}
+	var as []string
+	for a := range g.allowed {
+		as = append(as, a.String())
+	}
+	sort.Strings(as)
+	return fmt.Sprintf("FSProxy(%s, %s)", g.dir, strings.Join(as, ","))
+}