internal/datasource: consolidate

Merge ProxyDataSource and LocalDataSource into a single type,
DataSource.

Combine and simplify tests.

For golang/go#47780

Change-Id: I3510fee3a3d786705a2306a6233b352e5af40076
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/344952
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index 2fbd6a7..429a90c 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -20,6 +20,7 @@
 	"golang.org/x/pkgsite/internal/config"
 	"golang.org/x/pkgsite/internal/datasource"
 	"golang.org/x/pkgsite/internal/dcensus"
+	"golang.org/x/pkgsite/internal/fetch"
 	"golang.org/x/pkgsite/internal/frontend"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
@@ -76,13 +77,13 @@
 	}
 
 	if *directProxy {
-		var pds *datasource.ProxyDataSource
-		if *bypassLicenseCheck {
-			pds = datasource.NewBypassingLicenseCheck(proxyClient)
-		} else {
-			pds = datasource.NewProxy(proxyClient)
-		}
-		dsg = func(context.Context) internal.DataSource { return pds }
+		ds := datasource.Options{
+			Getters:              []fetch.ModuleGetter{fetch.NewProxyModuleGetter(proxyClient)},
+			ProxyClientForLatest: proxyClient,
+			BypassLicenseCheck:   *bypassLicenseCheck,
+			SourceClient:         source.NewClient(1 * time.Minute),
+		}.New()
+		dsg = func(context.Context) internal.DataSource { return ds }
 	} else {
 		db, err := cmdconfig.OpenDB(ctx, cfg, *bypassLicenseCheck)
 		if err != nil {
diff --git a/cmd/pkgsite/main.go b/cmd/pkgsite/main.go
index 184523b..4e6fab8 100644
--- a/cmd/pkgsite/main.go
+++ b/cmd/pkgsite/main.go
@@ -68,7 +68,11 @@
 
 func newServer(ctx context.Context, paths []string, gopathMode bool) (*frontend.Server, error) {
 	getters := buildGetters(ctx, paths, gopathMode)
-	lds := datasource.NewLocal(getters, source.NewClient(time.Second))
+	lds := datasource.Options{
+		Getters:            getters,
+		SourceClient:       source.NewClient(time.Second),
+		BypassLicenseCheck: true,
+	}.New()
 	server, err := frontend.NewServer(frontend.ServerConfig{
 		DataSourceGetter: func(context.Context) internal.DataSource { return lds },
 		StaticPath:       template.TrustedSourceFromFlag(flag.Lookup("static").Value),
diff --git a/internal/datasource/datasource.go b/internal/datasource/datasource.go
index 34e684a..3d88459 100644
--- a/internal/datasource/datasource.go
+++ b/internal/datasource/datasource.go
@@ -26,29 +26,38 @@
 	"golang.org/x/pkgsite/internal/version"
 )
 
-// dataSource implements the internal.DataSource interface, by trying a list of
+// DataSource implements the internal.DataSource interface, by trying a list of
 // fetch.ModuleGetters to fetch modules and caching the results.
-type dataSource struct {
-	getters            []fetch.ModuleGetter
-	sourceClient       *source.Client
-	bypassLicenseCheck bool
-	cache              *lru.Cache
-	prox               *proxy.Client // used for latest-version info only
-
+type DataSource struct {
+	opts  Options
+	cache *lru.Cache
 }
 
-func newDataSource(getters []fetch.ModuleGetter, sc *source.Client, bypassLicenseCheck bool, prox *proxy.Client) *dataSource {
+// Options are parameters for creating a new DataSource.
+type Options struct {
+	// List of getters to try, in order.
+	Getters []fetch.ModuleGetter
+	// If set, this will be used for latest-version information. To fetch modules from the proxy,
+	// include a ProxyModuleGetter in Getters.
+	ProxyClientForLatest *proxy.Client
+	SourceClient         *source.Client
+	BypassLicenseCheck   bool
+}
+
+// New creates a new DataSource from the options.
+func (o Options) New() *DataSource {
 	cache, err := lru.New(maxCachedModules)
 	if err != nil {
-		// Can only happen if size is bad.
+		// Can only happen if size is bad, and we control it.
 		panic(err)
 	}
-	return &dataSource{
-		getters:            getters,
-		sourceClient:       sc,
-		bypassLicenseCheck: bypassLicenseCheck,
-		cache:              cache,
-		prox:               prox,
+	opts := o
+	// Copy getters slice so caller doesn't modify us.
+	opts.Getters = make([]fetch.ModuleGetter, len(opts.Getters))
+	copy(opts.Getters, o.Getters)
+	return &DataSource{
+		opts:  opts,
+		cache: cache,
 	}
 }
 
@@ -61,7 +70,7 @@
 const maxCachedModules = 100
 
 // cacheGet returns information from the cache if it is present, and (nil, nil) otherwise.
-func (ds *dataSource) cacheGet(path, version string) (*internal.Module, error) {
+func (ds *DataSource) cacheGet(path, version string) (*internal.Module, error) {
 	// Look for an exact match first, then use LocalVersion, as for a
 	// directory-based or GOPATH-mode module.
 	for _, v := range []string{version, fetch.LocalVersion} {
@@ -74,13 +83,13 @@
 }
 
 // cachePut puts information into the cache.
-func (ds *dataSource) cachePut(path, version string, m *internal.Module, err error) {
+func (ds *DataSource) cachePut(path, version string, m *internal.Module, err error) {
 	ds.cache.Add(internal.Modver{Path: path, Version: version}, cacheEntry{m, err})
 }
 
 // getModule gets the module at the given path and version. It first checks the
 // cache, and if it isn't there it then tries to fetch it.
-func (ds *dataSource) getModule(ctx context.Context, modulePath, version string) (_ *internal.Module, err error) {
+func (ds *DataSource) getModule(ctx context.Context, modulePath, version string) (_ *internal.Module, err error) {
 	defer derrors.Wrap(&err, "getModule(%q, %q)", modulePath, version)
 
 	mod, err := ds.cacheGet(modulePath, version)
@@ -92,10 +101,10 @@
 	// module. At worst some work will be duplicated, but if that turns out to
 	// be a problem we could use golang.org/x/sync/singleflight.
 	m, err := ds.fetch(ctx, modulePath, version)
-	if m != nil && ds.prox != nil {
+	if m != nil && ds.opts.ProxyClientForLatest != nil {
 		// Use the go.mod file at the raw latest version to fill in deprecation
 		// and retraction information.
-		lmv, err2 := fetch.LatestModuleVersions(ctx, modulePath, ds.prox, nil)
+		lmv, err2 := fetch.LatestModuleVersions(ctx, modulePath, ds.opts.ProxyClientForLatest, nil)
 		if err2 != nil {
 			err = err2
 		} else {
@@ -112,18 +121,18 @@
 
 // fetch fetches a module using the configured ModuleGetters.
 // It tries each getter in turn until it finds one that has the module.
-func (ds *dataSource) fetch(ctx context.Context, modulePath, version string) (_ *internal.Module, err error) {
+func (ds *DataSource) fetch(ctx context.Context, modulePath, version string) (_ *internal.Module, err error) {
 	log.Infof(ctx, "DataSource: fetching %s@%s", modulePath, version)
 	start := time.Now()
 	defer func() {
 		log.Infof(ctx, "DataSource: fetched %s@%s in %s with error %v", modulePath, version, time.Since(start), err)
 	}()
-	for _, g := range ds.getters {
-		fr := fetch.FetchModule(ctx, modulePath, version, g, ds.sourceClient)
+	for _, g := range ds.opts.Getters {
+		fr := fetch.FetchModule(ctx, modulePath, version, g, ds.opts.SourceClient)
 		defer fr.Defer()
 		if fr.Error == nil {
 			m := fr.Module
-			if ds.bypassLicenseCheck {
+			if ds.opts.BypassLicenseCheck {
 				m.IsRedistributable = true
 				for _, unit := range m.Units {
 					unit.IsRedistributable = true
@@ -142,7 +151,7 @@
 
 // findModule finds the module with longest module path containing the given
 // package path. It returns an error if no module is found.
-func (ds *dataSource) findModule(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.Module, err error) {
+func (ds *DataSource) findModule(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.Module, err error) {
 	defer derrors.Wrap(&err, "findModule(%q, %q, %q)", pkgPath, modulePath, version)
 
 	if modulePath != internal.UnknownModulePath {
@@ -162,7 +171,7 @@
 }
 
 // GetUnitMeta returns information about a path.
-func (ds *dataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
+func (ds *DataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
 	defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, requestedModulePath, requestedVersion)
 
 	module, err := ds.findModule(ctx, path, requestedModulePath, requestedVersion)
@@ -182,7 +191,7 @@
 
 // GetUnit returns information about a unit. Both the module path and package
 // path must be known.
-func (ds *dataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
+func (ds *DataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
 	defer derrors.Wrap(&err, "GetUnit(%q, %q)", um.Path, um.ModulePath)
 
 	m, err := ds.getModule(ctx, um.ModulePath, um.Version)
@@ -206,10 +215,10 @@
 }
 
 // GetLatestInfo returns latest information for unitPath and modulePath.
-func (ds *dataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (latest internal.LatestInfo, err error) {
+func (ds *DataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (latest internal.LatestInfo, err error) {
 	defer derrors.Wrap(&err, "GetLatestInfo(ctx, %q, %q)", unitPath, modulePath)
 
-	if ds.prox == nil {
+	if ds.opts.ProxyClientForLatest == nil {
 		return internal.LatestInfo{}, nil
 	}
 
@@ -235,10 +244,10 @@
 // of the latest version found in the proxy by iterating through vN versions.
 // This function does not attempt to find whether the full path exists
 // in the new major version.
-func (ds *dataSource) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
+func (ds *DataSource) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
 	// We are checking if the full path is valid so that we can forward the error if not.
 	seriesPath := internal.SeriesPathForModule(modulePath)
-	info, err := ds.prox.Info(ctx, seriesPath, version.Latest)
+	info, err := ds.opts.ProxyClientForLatest.Info(ctx, seriesPath, version.Latest)
 	if err != nil {
 		return "", "", err
 	}
@@ -262,7 +271,7 @@
 	for v := startVersion; ; v++ {
 		query := fmt.Sprintf("%s/v%d", seriesPath, v)
 
-		_, err := ds.prox.Info(ctx, query, version.Latest)
+		_, err := ds.opts.ProxyClientForLatest.Info(ctx, query, version.Latest)
 		if errors.Is(err, derrors.NotFound) {
 			if v == 2 {
 				return modulePath, fullPath, nil
@@ -275,3 +284,13 @@
 		}
 	}
 }
+
+// GetNestedModules is not implemented.
+func (ds *DataSource) GetNestedModules(ctx context.Context, modulePath string) ([]*internal.ModuleInfo, error) {
+	return nil, nil
+}
+
+// GetModuleReadme is not implemented.
+func (*DataSource) GetModuleReadme(ctx context.Context, modulePath, resolvedVersion string) (*internal.Readme, error) {
+	return nil, nil
+}
diff --git a/internal/datasource/datasource_test.go b/internal/datasource/datasource_test.go
index 88b8978..dbaf735 100644
--- a/internal/datasource/datasource_test.go
+++ b/internal/datasource/datasource_test.go
@@ -5,15 +5,507 @@
 package datasource
 
 import (
+	"context"
+	"errors"
+	"fmt"
+	"log"
+	"os"
 	"testing"
+	"time"
 
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/google/safehtml/template"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/fetch"
+	"golang.org/x/pkgsite/internal/godoc/dochtml"
+	"golang.org/x/pkgsite/internal/licenses"
+	"golang.org/x/pkgsite/internal/proxy"
+	"golang.org/x/pkgsite/internal/proxy/proxytest"
+	"golang.org/x/pkgsite/internal/source"
+	"golang.org/x/pkgsite/internal/stdlib"
+	"golang.org/x/pkgsite/internal/testing/testhelper"
+	"golang.org/x/pkgsite/internal/version"
 )
 
+var (
+	defaultTestModules []*proxytest.Module
+	localGetters       []fetch.ModuleGetter
+)
+
+func TestMain(m *testing.M) {
+	dochtml.LoadTemplates(template.TrustedSourceFromConstant("../../static/doc"))
+	defaultTestModules = proxytest.LoadTestModules("../proxy/testdata")
+	var cleanup func()
+	localGetters, cleanup = buildLocalGetters()
+	defer cleanup()
+	licenses.OmitExceptions = true
+	os.Exit(m.Run())
+}
+
+func buildLocalGetters() ([]fetch.ModuleGetter, func()) {
+	modules := []map[string]string{
+		{
+			"go.mod":        "module github.com/my/module\n\ngo 1.12",
+			"LICENSE":       testhelper.BSD0License,
+			"README.md":     "README FILE FOR TESTING.",
+			"bar/COPYING":   testhelper.MITLicense,
+			"bar/README.md": "Another README FILE FOR TESTING.",
+			"bar/bar.go": `
+			// package bar
+			package bar
+
+			// Bar returns the string "bar".
+			func Bar() string {
+				return "bar"
+			}`,
+			"foo/LICENSE.md": testhelper.MITLicense,
+			"foo/foo.go": `
+			// package foo
+			package foo
+
+			import (
+				"fmt"
+
+				"github.com/my/module/bar"
+			)
+
+			// FooBar returns the string "foo bar".
+			func FooBar() string {
+				return fmt.Sprintf("foo %s", bar.Bar())
+			}`,
+		},
+		{
+			"go.mod":  "module github.com/no/license\n\ngo 1.12",
+			"LICENSE": "unknown",
+			"bar/bar.go": `
+			// package bar
+			package bar
+
+			// Bar returns the string "bar".
+			func Bar() string {
+				return "bar"
+			}`,
+		},
+	}
+
+	var (
+		dirs    []string
+		getters []fetch.ModuleGetter
+	)
+	for _, module := range modules {
+		directory, err := testhelper.CreateTestDirectory(module)
+		if err != nil {
+			log.Fatal(err)
+		}
+		dirs = append(dirs, directory)
+		mg, err := fetch.NewDirectoryModuleGetter("", directory)
+		if err != nil {
+			log.Fatal(err)
+		}
+		getters = append(getters, mg)
+	}
+	return getters, func() {
+		for _, d := range dirs {
+			os.RemoveAll(d)
+		}
+	}
+}
+
+func setup(t *testing.T, testModules []*proxytest.Module, bypassLicenseCheck bool) (context.Context, *DataSource, func()) {
+	t.Helper()
+	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
+
+	var client *proxy.Client
+	teardownProxy := func() {}
+	if testModules != nil {
+		client, teardownProxy = proxytest.SetupTestClient(t, testModules)
+	}
+
+	getters := localGetters
+	if testModules != nil {
+		getters = append(getters, fetch.NewProxyModuleGetter(client))
+	}
+
+	return ctx, Options{
+			Getters:              getters,
+			SourceClient:         source.NewClientForTesting(),
+			ProxyClientForLatest: client,
+			BypassLicenseCheck:   bypassLicenseCheck,
+		}.New(), func() {
+			teardownProxy()
+			cancel()
+		}
+}
+
+func TestProxyGetUnitMeta(t *testing.T) {
+	ctx, ds, teardown := setup(t, defaultTestModules, false)
+	defer teardown()
+
+	singleModInfo := internal.ModuleInfo{
+		ModulePath:        "example.com/single",
+		Version:           "v1.0.0",
+		IsRedistributable: true,
+		CommitTime:        proxytest.CommitTime,
+		HasGoMod:          true,
+	}
+
+	for _, test := range []struct {
+		path, modulePath, version string
+		want                      *internal.UnitMeta
+	}{
+		{
+			path:       "example.com/single",
+			modulePath: "example.com/single",
+			version:    "v1.0.0",
+			want: &internal.UnitMeta{
+				ModuleInfo:        singleModInfo,
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "example.com/single/pkg",
+			modulePath: "example.com/single",
+			version:    "v1.0.0",
+			want: &internal.UnitMeta{
+				ModuleInfo:        singleModInfo,
+				Name:              "pkg",
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "example.com/single/pkg",
+			modulePath: internal.UnknownModulePath,
+			version:    "v1.0.0",
+			want: &internal.UnitMeta{
+				ModuleInfo:        singleModInfo,
+				Name:              "pkg",
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "example.com/basic",
+			modulePath: internal.UnknownModulePath,
+			version:    version.Latest,
+			want: &internal.UnitMeta{
+				ModuleInfo: internal.ModuleInfo{
+					ModulePath:        "example.com/basic",
+					Version:           "v1.1.0",
+					IsRedistributable: true,
+					CommitTime:        proxytest.CommitTime,
+					HasGoMod:          true,
+				},
+				Name:              "basic",
+				IsRedistributable: true,
+			},
+		},
+	} {
+		t.Run(test.path, func(t *testing.T) {
+			got, err := ds.GetUnitMeta(ctx, test.path, test.modulePath, test.version)
+			if err != nil {
+				t.Fatal(err)
+			}
+			test.want.Path = test.path
+			if diff := cmp.Diff(test.want, got, cmpopts.IgnoreFields(internal.ModuleInfo{}, "SourceInfo")); diff != "" {
+				t.Errorf("mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestBypass(t *testing.T) {
+	for _, bypass := range []bool{false, true} {
+		t.Run(fmt.Sprintf("bypass=%t", bypass), func(t *testing.T) {
+			// re-create the data source to get around caching
+			ctx, ds, teardown := setup(t, defaultTestModules, bypass)
+			defer teardown()
+			for _, test := range []struct {
+				path      string
+				wantEmpty bool
+			}{
+				{"example.com/basic", false},
+				{"example.com/nonredist/unk", !bypass},
+			} {
+				t.Run(test.path, func(t *testing.T) {
+					um, err := ds.GetUnitMeta(ctx, test.path, internal.UnknownModulePath, "v1.0.0")
+					if err != nil {
+						t.Fatal(err)
+					}
+					got, err := ds.GetUnit(ctx, um, 0, internal.BuildContext{})
+					if err != nil {
+						t.Fatal(err)
+					}
+
+					// Assume internal.Module.RemoveNonRedistributableData is correct; we just
+					// need to check one value to confirm that it was called.
+					if gotEmpty := (got.Documentation == nil); gotEmpty != test.wantEmpty {
+						t.Errorf("got empty %t, want %t", gotEmpty, test.wantEmpty)
+					}
+				})
+			}
+		})
+	}
+}
+
+func TestGetLatestInfo(t *testing.T) {
+	testModules := []*proxytest.Module{
+		{
+			ModulePath: "foo.com/bar",
+			Version:    "v1.1.0",
+			Files: map[string]string{
+				"baz.go": "package bar",
+			},
+		},
+		{
+			ModulePath: "foo.com/bar/v2",
+			Version:    "v2.0.5",
+		},
+		{
+			ModulePath: "foo.com/bar/v3",
+		},
+		{
+			ModulePath: "bar.com/foo",
+			Version:    "v1.1.0",
+			Files: map[string]string{
+				"baz.go": "package foo",
+			},
+		},
+		{
+			ModulePath: "incompatible.com/bar",
+			Version:    "v2.1.1+incompatible",
+			Files: map[string]string{
+				"baz.go": "package bar",
+			},
+		},
+		{
+			ModulePath: "incompatible.com/bar/v3",
+		},
+	}
+	ctx, ds, teardown := setup(t, testModules, false)
+	defer teardown()
+	for _, test := range []struct {
+		fullPath        string
+		modulePath      string
+		wantModulePath  string
+		wantPackagePath string
+		wantErr         error
+	}{
+		{
+			fullPath:        "foo.com/bar",
+			modulePath:      "foo.com/bar",
+			wantModulePath:  "foo.com/bar/v3",
+			wantPackagePath: "foo.com/bar/v3",
+		},
+		{
+			fullPath:        "bar.com/foo",
+			modulePath:      "bar.com/foo",
+			wantModulePath:  "bar.com/foo",
+			wantPackagePath: "bar.com/foo",
+		},
+		{
+			fullPath:   "boo.com/far",
+			modulePath: "boo.com/far",
+			wantErr:    derrors.NotFound,
+		},
+		{
+			fullPath:        "foo.com/bar/baz",
+			modulePath:      "foo.com/bar",
+			wantModulePath:  "foo.com/bar/v3",
+			wantPackagePath: "foo.com/bar/v3",
+		},
+		{
+			fullPath:        "incompatible.com/bar",
+			modulePath:      "incompatible.com/bar",
+			wantModulePath:  "incompatible.com/bar/v3",
+			wantPackagePath: "incompatible.com/bar/v3",
+		},
+	} {
+		gotLatest, err := ds.GetLatestInfo(ctx, test.fullPath, test.modulePath, nil)
+		if err != nil {
+			if test.wantErr == nil {
+				t.Fatalf("got unexpected error %v", err)
+			}
+			if !errors.Is(err, test.wantErr) {
+				t.Errorf("got err = %v, want Is(%v)", err, test.wantErr)
+			}
+		}
+		if gotLatest.MajorModulePath != test.wantModulePath || gotLatest.MajorUnitPath != test.wantPackagePath {
+			t.Errorf("ds.GetLatestMajorVersion(%v, %v) = (%v, %v), want = (%v, %v)",
+				test.fullPath, test.modulePath, gotLatest.MajorModulePath, gotLatest.MajorUnitPath, test.wantModulePath, test.wantPackagePath)
+		}
+	}
+}
+
+func TestLocalGetUnitMeta(t *testing.T) {
+	ctx, ds, teardown := setup(t, nil, true)
+	defer teardown()
+
+	sourceInfo := source.NewGitHubInfo("https://github.com/my/module", "", "v0.0.0")
+	for _, test := range []struct {
+		path, modulePath string
+		want             *internal.UnitMeta
+		wantErr          error
+	}{
+		{
+			path:       "github.com/my/module",
+			modulePath: "github.com/my/module",
+			want: &internal.UnitMeta{
+				Path: "github.com/my/module",
+				ModuleInfo: internal.ModuleInfo{
+					ModulePath:        "github.com/my/module",
+					Version:           fetch.LocalVersion,
+					CommitTime:        fetch.LocalCommitTime,
+					IsRedistributable: true,
+					HasGoMod:          true,
+					SourceInfo:        sourceInfo,
+				},
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "github.com/my/module/bar",
+			modulePath: "github.com/my/module",
+			want: &internal.UnitMeta{
+				Path: "github.com/my/module/bar",
+				Name: "bar",
+				ModuleInfo: internal.ModuleInfo{
+					ModulePath:        "github.com/my/module",
+					Version:           fetch.LocalVersion,
+					CommitTime:        fetch.LocalCommitTime,
+					IsRedistributable: true,
+					HasGoMod:          true,
+					SourceInfo:        sourceInfo,
+				},
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "github.com/my/module/foo",
+			modulePath: "github.com/my/module",
+			want: &internal.UnitMeta{
+				Path: "github.com/my/module/foo",
+				Name: "foo",
+				ModuleInfo: internal.ModuleInfo{
+					ModulePath:        "github.com/my/module",
+					IsRedistributable: true,
+					Version:           fetch.LocalVersion,
+					CommitTime:        fetch.LocalCommitTime,
+					HasGoMod:          true,
+					SourceInfo:        sourceInfo,
+				},
+				IsRedistributable: true,
+			},
+		},
+		{
+			path:       "github.com/my/module/bar",
+			modulePath: internal.UnknownModulePath,
+			want: &internal.UnitMeta{
+				Path:              "github.com/my/module/bar",
+				Name:              "bar",
+				IsRedistributable: true,
+				ModuleInfo: internal.ModuleInfo{
+					ModulePath:        "github.com/my/module",
+					Version:           fetch.LocalVersion,
+					CommitTime:        fetch.LocalCommitTime,
+					IsRedistributable: true,
+					HasGoMod:          true,
+					SourceInfo:        sourceInfo,
+				},
+			},
+		},
+		{
+			path:       "github.com/not/loaded",
+			modulePath: internal.UnknownModulePath,
+			wantErr:    derrors.NotFound,
+		},
+		{
+			path:       "net/http",
+			modulePath: stdlib.ModulePath,
+			wantErr:    derrors.InvalidArgument,
+		},
+	} {
+		t.Run(test.path, func(t *testing.T) {
+			got, err := ds.GetUnitMeta(ctx, test.path, test.modulePath, fetch.LocalVersion)
+			if test.wantErr != nil {
+				if !errors.Is(err, test.wantErr) {
+					t.Errorf("GetUnitMeta(%q, %q): %v; wantErr = %v)", test.path, test.modulePath, err, test.wantErr)
+				}
+			} else {
+				if err != nil {
+					t.Fatal(err)
+				}
+				if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(source.Info{})); diff != "" {
+					t.Errorf("mismatch (-want +got):\n%s", diff)
+
+				}
+			}
+		})
+	}
+}
+
+func TestLocalGetUnit(t *testing.T) {
+	// This is a simple test to verify that data is fetched correctly. The
+	// return value of FetchResult is tested in internal/fetch so no need
+	// to repeat it.
+	ctx, ds, teardown := setup(t, nil, true)
+	defer teardown()
+
+	for _, test := range []struct {
+		path, modulePath string
+		wantLoaded       bool
+	}{
+		{
+			path:       "github.com/my/module",
+			modulePath: "github.com/my/module",
+			wantLoaded: true,
+		},
+		{
+			path:       "github.com/my/module/foo",
+			modulePath: "github.com/my/module",
+			wantLoaded: true,
+		},
+		{
+			path:       "github.com/no/license/bar",
+			modulePath: "github.com/no/license",
+			wantLoaded: true,
+		},
+		{
+			path:       "github.com/not/loaded",
+			modulePath: internal.UnknownModulePath,
+		},
+	} {
+		t.Run(test.path, func(t *testing.T) {
+			um := &internal.UnitMeta{
+				Path:       test.path,
+				ModuleInfo: internal.ModuleInfo{ModulePath: test.modulePath},
+			}
+			got, err := ds.GetUnit(ctx, um, 0, internal.BuildContext{})
+			if !test.wantLoaded {
+				if err == nil {
+					t.Fatal("returned not loaded module")
+				}
+				return
+			}
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if gotEmpty := (got.Documentation == nil && got.Readme == nil); gotEmpty {
+				t.Errorf("gotEmpty = %t", gotEmpty)
+			}
+			if got.Documentation != nil {
+				want := []internal.BuildContext{internal.BuildContextAll}
+				if !cmp.Equal(got.BuildContexts, want) {
+					t.Errorf("got %v, want %v", got.BuildContexts, want)
+				}
+			}
+		})
+	}
+}
+
 func TestCache(t *testing.T) {
-	ds := newDataSource(nil, nil, false, nil)
+	ds := Options{}.New()
 	m1 := &internal.Module{}
 	ds.cachePut("m1", fetch.LocalVersion, m1, nil)
 	ds.cachePut("m2", "v1.0.0", nil, derrors.NotFound)
diff --git a/internal/datasource/gopath_getter.go b/internal/datasource/gopath_getter.go
new file mode 100644
index 0000000..26f19c3
--- /dev/null
+++ b/internal/datasource/gopath_getter.go
@@ -0,0 +1,41 @@
+// Copyright 2021 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 datasource
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"golang.org/x/pkgsite/internal/derrors"
+	"golang.org/x/pkgsite/internal/fetch"
+)
+
+// NewGOPATHModuleGetter returns a module getter that uses the GOPATH
+// environment variable to find the module with the given import path.
+func NewGOPATHModuleGetter(importPath string) (_ fetch.ModuleGetter, err error) {
+	defer derrors.Wrap(&err, "NewGOPATHModuleGetter(%q)", importPath)
+
+	dir := getFullPath(importPath)
+	if dir == "" {
+		return nil, fmt.Errorf("path %s doesn't exist: %w", importPath, derrors.NotFound)
+	}
+	return fetch.NewDirectoryModuleGetter(importPath, dir)
+}
+
+// getFullPath takes an import path, tests it relative to each GOPATH, and returns
+// a full path to the module. If the given import path doesn't exist in any GOPATH,
+// an empty string is returned.
+func getFullPath(modulePath string) string {
+	gopaths := filepath.SplitList(os.Getenv("GOPATH"))
+	for _, gopath := range gopaths {
+		path := filepath.Join(gopath, "src", modulePath)
+		info, err := os.Stat(path)
+		if err == nil && info.IsDir() {
+			return path
+		}
+	}
+	return ""
+}
diff --git a/internal/datasource/local.go b/internal/datasource/local.go
deleted file mode 100644
index feffa36..0000000
--- a/internal/datasource/local.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 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 datasource
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/fetch"
-	"golang.org/x/pkgsite/internal/source"
-)
-
-// LocalDataSource implements an in-memory internal.DataSource used to display documentation
-// locally. It is not backed by a database or a proxy instance.
-type LocalDataSource struct {
-	sourceClient *source.Client
-	ds           *dataSource
-}
-
-// New creates and returns a new local datasource that bypasses license
-// checks by default.
-func NewLocal(getters []fetch.ModuleGetter, sc *source.Client) *LocalDataSource {
-	return &LocalDataSource{
-		sourceClient: sc,
-		ds:           newDataSource(getters, sc, true, nil),
-	}
-}
-
-// NewGOPATHModuleGetter returns a module getter that uses the GOPATH
-// environment variable to find the module with the given import path.
-func NewGOPATHModuleGetter(importPath string) (_ fetch.ModuleGetter, err error) {
-	defer derrors.Wrap(&err, "NewGOPATHModuleGetter(%q)", importPath)
-
-	dir := getFullPath(importPath)
-	if dir == "" {
-		return nil, fmt.Errorf("path %s doesn't exist: %w", importPath, derrors.NotFound)
-	}
-	return fetch.NewDirectoryModuleGetter(importPath, dir)
-}
-
-// getFullPath takes an import path, tests it relative to each GOPATH, and returns
-// a full path to the module. If the given import path doesn't exist in any GOPATH,
-// an empty string is returned.
-func getFullPath(modulePath string) string {
-	gopaths := filepath.SplitList(os.Getenv("GOPATH"))
-	for _, gopath := range gopaths {
-		path := filepath.Join(gopath, "src", modulePath)
-		info, err := os.Stat(path)
-		if err == nil && info.IsDir() {
-			return path
-		}
-	}
-	return ""
-}
-
-func (ds *LocalDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
-	return ds.ds.GetUnitMeta(ctx, path, requestedModulePath, requestedVersion)
-}
-
-// GetUnit returns information about a unit. Both the module path and package
-// path must be known.
-func (ds *LocalDataSource) GetUnit(ctx context.Context, pathInfo *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
-	return ds.ds.GetUnit(ctx, pathInfo, fields, bc)
-}
-
-// GetLatestInfo is not implemented.
-func (ds *LocalDataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (internal.LatestInfo, error) {
-	return ds.ds.GetLatestInfo(ctx, unitPath, modulePath, latestUnitMeta)
-}
-
-// GetNestedModules is not implemented.
-func (ds *LocalDataSource) GetNestedModules(ctx context.Context, modulePath string) ([]*internal.ModuleInfo, error) {
-	return nil, nil
-}
-
-// GetModuleReadme is not implemented.
-func (*LocalDataSource) GetModuleReadme(ctx context.Context, modulePath, resolvedVersion string) (*internal.Readme, error) {
-	return nil, nil
-}
diff --git a/internal/datasource/local_test.go b/internal/datasource/local_test.go
deleted file mode 100644
index 9f0ee78..0000000
--- a/internal/datasource/local_test.go
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright 2020 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 datasource
-
-import (
-	"context"
-	"errors"
-	"log"
-	"os"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/fetch"
-	"golang.org/x/pkgsite/internal/source"
-	"golang.org/x/pkgsite/internal/stdlib"
-	"golang.org/x/pkgsite/internal/testing/testhelper"
-)
-
-var datasource *LocalDataSource
-
-func setupLocal() func() {
-	modules := []map[string]string{
-		{
-			"go.mod":        "module github.com/my/module\n\ngo 1.12",
-			"LICENSE":       testhelper.BSD0License,
-			"README.md":     "README FILE FOR TESTING.",
-			"bar/COPYING":   testhelper.MITLicense,
-			"bar/README.md": "Another README FILE FOR TESTING.",
-			"bar/bar.go": `
-			// package bar
-			package bar
-
-			// Bar returns the string "bar".
-			func Bar() string {
-				return "bar"
-			}`,
-			"foo/LICENSE.md": testhelper.MITLicense,
-			"foo/foo.go": `
-			// package foo
-			package foo
-
-			import (
-				"fmt"
-
-				"github.com/my/module/bar"
-			)
-
-			// FooBar returns the string "foo bar".
-			func FooBar() string {
-				return fmt.Sprintf("foo %s", bar.Bar())
-			}`,
-		},
-		{
-			"go.mod":  "module github.com/no/license\n\ngo 1.12",
-			"LICENSE": "unknown",
-			"bar/bar.go": `
-			// package bar
-			package bar
-
-			// Bar returns the string "bar".
-			func Bar() string {
-				return "bar"
-			}`,
-		},
-	}
-
-	var (
-		dirs    []string
-		getters []fetch.ModuleGetter
-	)
-	for _, module := range modules {
-		directory, err := testhelper.CreateTestDirectory(module)
-		if err != nil {
-			log.Fatal(err)
-		}
-		dirs = append(dirs, directory)
-		mg, err := fetch.NewDirectoryModuleGetter("", directory)
-		if err != nil {
-			log.Fatal(err)
-		}
-		getters = append(getters, mg)
-	}
-	datasource = NewLocal(getters, source.NewClientForTesting())
-
-	return func() {
-		for _, d := range dirs {
-			os.RemoveAll(d)
-		}
-	}
-}
-
-func TestLocalGetUnitMeta(t *testing.T) {
-	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
-	defer cancel()
-
-	sourceInfo := source.NewGitHubInfo("https://github.com/my/module", "", "v0.0.0")
-	for _, test := range []struct {
-		path, modulePath string
-		want             *internal.UnitMeta
-		wantErr          error
-	}{
-		{
-			path:       "github.com/my/module",
-			modulePath: "github.com/my/module",
-			want: &internal.UnitMeta{
-				Path: "github.com/my/module",
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "github.com/my/module",
-					Version:           fetch.LocalVersion,
-					CommitTime:        fetch.LocalCommitTime,
-					IsRedistributable: true,
-					HasGoMod:          true,
-					SourceInfo:        sourceInfo,
-				},
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "github.com/my/module/bar",
-			modulePath: "github.com/my/module",
-			want: &internal.UnitMeta{
-				Path: "github.com/my/module/bar",
-				Name: "bar",
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "github.com/my/module",
-					Version:           fetch.LocalVersion,
-					CommitTime:        fetch.LocalCommitTime,
-					IsRedistributable: true,
-					HasGoMod:          true,
-					SourceInfo:        sourceInfo,
-				},
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "github.com/my/module/foo",
-			modulePath: "github.com/my/module",
-			want: &internal.UnitMeta{
-				Path: "github.com/my/module/foo",
-				Name: "foo",
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "github.com/my/module",
-					IsRedistributable: true,
-					Version:           fetch.LocalVersion,
-					CommitTime:        fetch.LocalCommitTime,
-					HasGoMod:          true,
-					SourceInfo:        sourceInfo,
-				},
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "github.com/my/module/bar",
-			modulePath: internal.UnknownModulePath,
-			want: &internal.UnitMeta{
-				Path:              "github.com/my/module/bar",
-				Name:              "bar",
-				IsRedistributable: true,
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "github.com/my/module",
-					Version:           fetch.LocalVersion,
-					CommitTime:        fetch.LocalCommitTime,
-					IsRedistributable: true,
-					HasGoMod:          true,
-					SourceInfo:        sourceInfo,
-				},
-			},
-		},
-		{
-			path:       "github.com/not/loaded",
-			modulePath: internal.UnknownModulePath,
-			wantErr:    derrors.NotFound,
-		},
-		{
-			path:       "net/http",
-			modulePath: stdlib.ModulePath,
-			wantErr:    derrors.InvalidArgument,
-		},
-	} {
-		t.Run(test.path, func(t *testing.T) {
-			got, err := datasource.GetUnitMeta(ctx, test.path, test.modulePath, fetch.LocalVersion)
-			if test.wantErr != nil {
-				if !errors.Is(err, test.wantErr) {
-					t.Errorf("GetUnitMeta(%q, %q): %v; wantErr = %v)", test.path, test.modulePath, err, test.wantErr)
-				}
-			} else {
-				if err != nil {
-					t.Fatal(err)
-				}
-				if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(source.Info{})); diff != "" {
-					t.Errorf("mismatch (-want +got):\n%s", diff)
-
-				}
-			}
-		})
-	}
-}
-
-func TestLocalGetUnit(t *testing.T) {
-	// This is a simple test to verify that data is fetched correctly. The
-	// return value of FetchResult is tested in internal/fetch so no need
-	// to repeat it.
-	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
-	defer cancel()
-
-	for _, test := range []struct {
-		path, modulePath string
-		wantLoaded       bool
-	}{
-		{
-			path:       "github.com/my/module",
-			modulePath: "github.com/my/module",
-			wantLoaded: true,
-		},
-		{
-			path:       "github.com/my/module/foo",
-			modulePath: "github.com/my/module",
-			wantLoaded: true,
-		},
-		{
-			path:       "github.com/no/license/bar",
-			modulePath: "github.com/no/license",
-			wantLoaded: true,
-		},
-		{
-			path:       "github.com/not/loaded",
-			modulePath: internal.UnknownModulePath,
-		},
-	} {
-		t.Run(test.path, func(t *testing.T) {
-			um := &internal.UnitMeta{
-				Path:       test.path,
-				ModuleInfo: internal.ModuleInfo{ModulePath: test.modulePath},
-			}
-			got, err := datasource.GetUnit(ctx, um, 0, internal.BuildContext{})
-			if !test.wantLoaded {
-				if err == nil {
-					t.Fatal("returned not loaded module")
-				}
-				return
-			}
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			if gotEmpty := (got.Documentation == nil && got.Readme == nil); gotEmpty {
-				t.Errorf("gotEmpty = %t", gotEmpty)
-			}
-			if got.Documentation != nil {
-				want := []internal.BuildContext{internal.BuildContextAll}
-				if !cmp.Equal(got.BuildContexts, want) {
-					t.Errorf("got %v, want %v", got.BuildContexts, want)
-				}
-			}
-		})
-	}
-}
diff --git a/internal/datasource/proxy.go b/internal/datasource/proxy.go
deleted file mode 100644
index bafd6d5..0000000
--- a/internal/datasource/proxy.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2019 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 datasource
-
-import (
-	"context"
-	"time"
-
-	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/fetch"
-	"golang.org/x/pkgsite/internal/proxy"
-	"golang.org/x/pkgsite/internal/source"
-)
-
-var _ internal.DataSource = (*ProxyDataSource)(nil)
-
-// New returns a new direct proxy datasource.
-func NewProxy(proxyClient *proxy.Client) *ProxyDataSource {
-	return newProxyDataSource(proxyClient, source.NewClient(1*time.Minute), false)
-}
-
-func NewForTesting(proxyClient *proxy.Client, bypassLicenseCheck bool) *ProxyDataSource {
-	return newProxyDataSource(proxyClient, source.NewClientForTesting(), bypassLicenseCheck)
-}
-
-func newProxyDataSource(proxyClient *proxy.Client, sourceClient *source.Client, bypassLicenseCheck bool) *ProxyDataSource {
-	ds := newDataSource([]fetch.ModuleGetter{fetch.NewProxyModuleGetter(proxyClient)}, sourceClient, bypassLicenseCheck, proxyClient)
-	return &ProxyDataSource{
-		ds: ds,
-	}
-}
-
-// NewBypassingLicenseCheck returns a new direct proxy datasource that bypasses
-// license checks. That means all data will be returned for non-redistributable
-// modules, packages and directories.
-func NewBypassingLicenseCheck(c *proxy.Client) *ProxyDataSource {
-	return newProxyDataSource(c, source.NewClient(1*time.Minute), true)
-}
-
-// ProxyDataSource implements the frontend.DataSource interface, by querying a
-// module proxy directly and caching the results in memory.
-type ProxyDataSource struct {
-	ds *dataSource
-}
-
-// GetLatestInfo returns latest information for unitPath and modulePath.
-func (ds *ProxyDataSource) GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *internal.UnitMeta) (latest internal.LatestInfo, err error) {
-	return ds.ds.GetLatestInfo(ctx, unitPath, modulePath, latestUnitMeta)
-}
diff --git a/internal/datasource/proxy_details.go b/internal/datasource/proxy_details.go
deleted file mode 100644
index 77f29fa..0000000
--- a/internal/datasource/proxy_details.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 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 datasource
-
-import (
-	"context"
-
-	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/derrors"
-)
-
-// GetUnit returns information about a directory at a path.
-func (ds *ProxyDataSource) GetUnit(ctx context.Context, um *internal.UnitMeta, field internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
-	defer derrors.Wrap(&err, "GetUnit(%q, %q, %q)", um.Path, um.ModulePath, um.Version)
-	return ds.ds.GetUnit(ctx, um, field, bc)
-}
-
-func (ds *ProxyDataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
-	return ds.ds.GetUnitMeta(ctx, path, requestedModulePath, requestedVersion)
-}
-
-// GetExperiments is unimplemented.
-func (*ProxyDataSource) GetExperiments(ctx context.Context) ([]*internal.Experiment, error) {
-	return nil, nil
-}
-
-// GetNestedModules will return an empty slice since it is not implemented in proxy mode.
-func (ds *ProxyDataSource) GetNestedModules(ctx context.Context, modulePath string) (_ []*internal.ModuleInfo, err error) {
-	return nil, nil
-}
-
-// GetModuleReadme is unimplemented.
-func (ds *ProxyDataSource) GetModuleReadme(ctx context.Context, modulePath, resolvedVersion string) (*internal.Readme, error) {
-	return nil, nil
-}
diff --git a/internal/datasource/proxy_test.go b/internal/datasource/proxy_test.go
deleted file mode 100644
index 9730c66..0000000
--- a/internal/datasource/proxy_test.go
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2019 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 datasource
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"os"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-	"github.com/google/safehtml/template"
-	"golang.org/x/pkgsite/internal"
-	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/godoc/dochtml"
-	"golang.org/x/pkgsite/internal/licenses"
-	"golang.org/x/pkgsite/internal/proxy/proxytest"
-	"golang.org/x/pkgsite/internal/testing/sample"
-	"golang.org/x/pkgsite/internal/version"
-)
-
-var testModules []*proxytest.Module
-
-func TestMain(m *testing.M) {
-	dochtml.LoadTemplates(template.TrustedSourceFromConstant("../../static/doc"))
-	testModules = proxytest.LoadTestModules("../proxy/testdata")
-	licenses.OmitExceptions = true
-	defer setupLocal()()
-	os.Exit(m.Run())
-}
-
-func setup(t *testing.T, bypassLicenseCheck bool) (context.Context, *ProxyDataSource, func()) {
-	t.Helper()
-	client, teardownProxy := proxytest.SetupTestClient(t, testModules)
-	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
-	return ctx, NewForTesting(client, bypassLicenseCheck), func() {
-		teardownProxy()
-		cancel()
-	}
-}
-
-var (
-	wantLicenseMD = sample.LicenseMetadata()[0]
-	wantPackage   = internal.Unit{
-		UnitMeta: internal.UnitMeta{
-			Path: "foo.com/bar/baz",
-			Name: "baz",
-			ModuleInfo: internal.ModuleInfo{
-				ModulePath:        "foo.com/bar",
-				Version:           "v1.2.0",
-				CommitTime:        time.Date(2019, 1, 30, 0, 0, 0, 0, time.UTC),
-				IsRedistributable: true,
-			},
-			Licenses:          []*licenses.Metadata{wantLicenseMD},
-			IsRedistributable: true,
-		},
-		Imports: []string{"net/http"},
-		Documentation: []*internal.Documentation{{
-			Synopsis: "Package baz provides a helpful constant.",
-			GOOS:     "linux",
-			GOARCH:   "amd64",
-		}},
-	}
-)
-
-func TestProxyGetUnitMeta(t *testing.T) {
-	ctx, ds, teardown := setup(t, false)
-	defer teardown()
-
-	singleModInfo := internal.ModuleInfo{
-		ModulePath:        "example.com/single",
-		Version:           "v1.0.0",
-		IsRedistributable: true,
-		CommitTime:        proxytest.CommitTime,
-		HasGoMod:          true,
-	}
-
-	for _, test := range []struct {
-		path, modulePath, version string
-		want                      *internal.UnitMeta
-	}{
-		{
-			path:       "example.com/single",
-			modulePath: "example.com/single",
-			version:    "v1.0.0",
-			want: &internal.UnitMeta{
-				ModuleInfo:        singleModInfo,
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "example.com/single/pkg",
-			modulePath: "example.com/single",
-			version:    "v1.0.0",
-			want: &internal.UnitMeta{
-				ModuleInfo:        singleModInfo,
-				Name:              "pkg",
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "example.com/single/pkg",
-			modulePath: internal.UnknownModulePath,
-			version:    "v1.0.0",
-			want: &internal.UnitMeta{
-				ModuleInfo:        singleModInfo,
-				Name:              "pkg",
-				IsRedistributable: true,
-			},
-		},
-		{
-			path:       "example.com/basic",
-			modulePath: internal.UnknownModulePath,
-			version:    version.Latest,
-			want: &internal.UnitMeta{
-				ModuleInfo: internal.ModuleInfo{
-					ModulePath:        "example.com/basic",
-					Version:           "v1.1.0",
-					IsRedistributable: true,
-					CommitTime:        proxytest.CommitTime,
-					HasGoMod:          true,
-				},
-				Name:              "basic",
-				IsRedistributable: true,
-			},
-		},
-	} {
-		t.Run(test.path, func(t *testing.T) {
-			got, err := ds.GetUnitMeta(ctx, test.path, test.modulePath, test.version)
-			if err != nil {
-				t.Fatal(err)
-			}
-			test.want.Path = test.path
-			if diff := cmp.Diff(test.want, got, cmpopts.IgnoreFields(internal.ModuleInfo{}, "SourceInfo")); diff != "" {
-				t.Errorf("mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestBypass(t *testing.T) {
-	for _, bypass := range []bool{false, true} {
-		t.Run(fmt.Sprintf("bypass=%t", bypass), func(t *testing.T) {
-			// re-create the data source to get around caching
-			ctx, ds, teardown := setup(t, bypass)
-			defer teardown()
-			for _, test := range []struct {
-				path      string
-				wantEmpty bool
-			}{
-				{"example.com/basic", false},
-				{"example.com/nonredist/unk", !bypass},
-			} {
-				t.Run(test.path, func(t *testing.T) {
-					um, err := ds.GetUnitMeta(ctx, test.path, internal.UnknownModulePath, "v1.0.0")
-					if err != nil {
-						t.Fatal(err)
-					}
-					got, err := ds.GetUnit(ctx, um, 0, internal.BuildContext{})
-					if err != nil {
-						t.Fatal(err)
-					}
-
-					// Assume internal.Module.RemoveNonRedistributableData is correct; we just
-					// need to check one value to confirm that it was called.
-					if gotEmpty := (got.Documentation == nil); gotEmpty != test.wantEmpty {
-						t.Errorf("got empty %t, want %t", gotEmpty, test.wantEmpty)
-					}
-				})
-			}
-		})
-	}
-}
-
-func TestGetLatestInfo(t *testing.T) {
-	t.Helper()
-	testModules := []*proxytest.Module{
-		{
-			ModulePath: "foo.com/bar",
-			Version:    "v1.1.0",
-			Files: map[string]string{
-				"baz.go": "package bar",
-			},
-		},
-		{
-			ModulePath: "foo.com/bar/v2",
-			Version:    "v2.0.5",
-		},
-		{
-			ModulePath: "foo.com/bar/v3",
-		},
-		{
-			ModulePath: "bar.com/foo",
-			Version:    "v1.1.0",
-			Files: map[string]string{
-				"baz.go": "package foo",
-			},
-		},
-		{
-			ModulePath: "incompatible.com/bar",
-			Version:    "v2.1.1+incompatible",
-			Files: map[string]string{
-				"baz.go": "package bar",
-			},
-		},
-		{
-			ModulePath: "incompatible.com/bar/v3",
-		},
-	}
-	client, teardownProxy := proxytest.SetupTestClient(t, testModules)
-	defer teardownProxy()
-	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-	defer cancel()
-	ds := NewForTesting(client, false)
-
-	for _, test := range []struct {
-		fullPath        string
-		modulePath      string
-		wantModulePath  string
-		wantPackagePath string
-		wantErr         error
-	}{
-		{
-			fullPath:        "foo.com/bar",
-			modulePath:      "foo.com/bar",
-			wantModulePath:  "foo.com/bar/v3",
-			wantPackagePath: "foo.com/bar/v3",
-		},
-		{
-			fullPath:        "bar.com/foo",
-			modulePath:      "bar.com/foo",
-			wantModulePath:  "bar.com/foo",
-			wantPackagePath: "bar.com/foo",
-		},
-		{
-			fullPath:   "boo.com/far",
-			modulePath: "boo.com/far",
-			wantErr:    derrors.NotFound,
-		},
-		{
-			fullPath:        "foo.com/bar/baz",
-			modulePath:      "foo.com/bar",
-			wantModulePath:  "foo.com/bar/v3",
-			wantPackagePath: "foo.com/bar/v3",
-		},
-		{
-			fullPath:        "incompatible.com/bar",
-			modulePath:      "incompatible.com/bar",
-			wantModulePath:  "incompatible.com/bar/v3",
-			wantPackagePath: "incompatible.com/bar/v3",
-		},
-	} {
-		gotLatest, err := ds.GetLatestInfo(ctx, test.fullPath, test.modulePath, nil)
-		if err != nil {
-			if test.wantErr == nil {
-				t.Fatalf("got unexpected error %v", err)
-			}
-			if !errors.Is(err, test.wantErr) {
-				t.Errorf("got err = %v, want Is(%v)", err, test.wantErr)
-			}
-		}
-		if gotLatest.MajorModulePath != test.wantModulePath || gotLatest.MajorUnitPath != test.wantPackagePath {
-			t.Errorf("ds.GetLatestMajorVersion(%v, %v) = (%v, %v), want = (%v, %v)",
-				test.fullPath, test.modulePath, gotLatest.MajorModulePath, gotLatest.MajorUnitPath, test.wantModulePath, test.wantPackagePath)
-		}
-	}
-}