github.com/windmeup/goreleaser@v1.21.95/internal/pipe/nix/nix_test.go (about)

     1  package nix
     2  
     3  import (
     4  	"html/template"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/windmeup/goreleaser/internal/artifact"
    13  	"github.com/windmeup/goreleaser/internal/client"
    14  	"github.com/windmeup/goreleaser/internal/golden"
    15  	"github.com/windmeup/goreleaser/internal/skips"
    16  	"github.com/windmeup/goreleaser/internal/testctx"
    17  	"github.com/windmeup/goreleaser/pkg/config"
    18  )
    19  
    20  func TestContinueOnError(t *testing.T) {
    21  	require.True(t, Pipe{}.ContinueOnError())
    22  }
    23  
    24  func TestString(t *testing.T) {
    25  	require.NotEmpty(t, Pipe{}.String())
    26  }
    27  
    28  func TestSkip(t *testing.T) {
    29  	t.Run("no-nix", func(t *testing.T) {
    30  		require.True(t, Pipe{}.Skip(testctx.New()))
    31  	})
    32  	t.Run("skip flag", func(t *testing.T) {
    33  		require.True(t, NewPublish().Skip(testctx.NewWithCfg(config.Project{
    34  			Nix: []config.Nix{{}},
    35  		}, testctx.Skip(skips.Nix))))
    36  	})
    37  	t.Run("nix-all-good", func(t *testing.T) {
    38  		require.False(t, NewPublish().Skip(testctx.NewWithCfg(config.Project{
    39  			Nix: []config.Nix{{}},
    40  		})))
    41  	})
    42  	t.Run("prefetcher-not-in-path", func(t *testing.T) {
    43  		t.Setenv("PATH", "nope")
    44  		require.True(t, NewPublish().Skip(testctx.NewWithCfg(config.Project{
    45  			Nix: []config.Nix{{}},
    46  		})))
    47  	})
    48  }
    49  
    50  const fakeNixPrefetchURLBin = "fake-nix-prefetch-url"
    51  
    52  func TestPrefetcher(t *testing.T) {
    53  	t.Run("prefetch", func(t *testing.T) {
    54  		t.Run("build", func(t *testing.T) {
    55  			sha, err := buildShaPrefetcher{}.Prefetch("any")
    56  			require.NoError(t, err)
    57  			require.Equal(t, zeroHash, sha)
    58  		})
    59  		t.Run("publish", func(t *testing.T) {
    60  			t.Run("no-nix-prefetch-url", func(t *testing.T) {
    61  				_, err := publishShaPrefetcher{fakeNixPrefetchURLBin}.Prefetch("any")
    62  				require.ErrorIs(t, err, exec.ErrNotFound)
    63  			})
    64  			t.Run("valid", func(t *testing.T) {
    65  				sha, err := publishShaPrefetcher{nixPrefetchURLBin}.Prefetch("https://github.com/windmeup/goreleaser/releases/download/v1.18.2/goreleaser_Darwin_arm64.tar.gz")
    66  				require.NoError(t, err)
    67  				require.Equal(t, "0girjxp07srylyq36xk1ska8p68m2fhp05xgyv4wkcl61d6rzv3y", sha)
    68  			})
    69  		})
    70  	})
    71  	t.Run("available", func(t *testing.T) {
    72  		t.Run("build", func(t *testing.T) {
    73  			require.True(t, buildShaPrefetcher{}.Available())
    74  		})
    75  		t.Run("publish", func(t *testing.T) {
    76  			t.Run("no-nix-prefetch-url", func(t *testing.T) {
    77  				require.False(t, publishShaPrefetcher{fakeNixPrefetchURLBin}.Available())
    78  			})
    79  			t.Run("valid", func(t *testing.T) {
    80  				require.True(t, publishShaPrefetcher{nixPrefetchURLBin}.Available())
    81  			})
    82  		})
    83  	})
    84  }
    85  
    86  func TestRunPipe(t *testing.T) {
    87  	for _, tt := range []struct {
    88  		name                 string
    89  		expectRunErrorIs     error
    90  		expectPublishErrorIs error
    91  		nix                  config.Nix
    92  	}{
    93  		{
    94  			name: "minimal",
    95  			nix: config.Nix{
    96  				IDs: []string{"foo"},
    97  				Repository: config.RepoRef{
    98  					Owner: "foo",
    99  					Name:  "bar",
   100  				},
   101  			},
   102  		},
   103  		{
   104  			name: "deps",
   105  			nix: config.Nix{
   106  				IDs: []string{"foo"},
   107  				Repository: config.RepoRef{
   108  					Owner: "foo",
   109  					Name:  "bar",
   110  				},
   111  				Dependencies: []config.NixDependency{
   112  					{Name: "fish"},
   113  					{Name: "bash"},
   114  					linuxDep("ttyd"),
   115  					darwinDep("chromium"),
   116  				},
   117  			},
   118  		},
   119  		{
   120  			name: "extra-install",
   121  			nix: config.Nix{
   122  				IDs: []string{"foo"},
   123  				Repository: config.RepoRef{
   124  					Owner: "foo",
   125  					Name:  "bar",
   126  				},
   127  				Dependencies: []config.NixDependency{
   128  					{Name: "fish"},
   129  					{Name: "bash"},
   130  					linuxDep("ttyd"),
   131  					darwinDep("chromium"),
   132  				},
   133  				ExtraInstall: "installManPage ./manpages/foo.1.gz",
   134  			},
   135  		},
   136  		{
   137  			name: "open-pr",
   138  			nix: config.Nix{
   139  				Name:        "foo",
   140  				IDs:         []string{"foo"},
   141  				Description: "my test",
   142  				Homepage:    "https://goreleaser.com",
   143  				License:     "mit",
   144  				Path:        "pkgs/foo.nix",
   145  				Repository: config.RepoRef{
   146  					Owner:  "foo",
   147  					Name:   "bar",
   148  					Branch: "update-{{.Version}}",
   149  					PullRequest: config.PullRequest{
   150  						Enabled: true,
   151  					},
   152  				},
   153  			},
   154  		},
   155  		{
   156  			name: "wrapped-in-dir",
   157  			nix: config.Nix{
   158  				Name:        "wrapped-in-dir",
   159  				IDs:         []string{"wrapped-in-dir"},
   160  				Description: "my test",
   161  				Homepage:    "https://goreleaser.com",
   162  				License:     "mit",
   163  				PostInstall: `
   164  					echo "do something"
   165  				`,
   166  				Install: `
   167  					mkdir -p $out/bin
   168  					cp foo $out/bin/foo
   169  				`,
   170  				ExtraInstall: "installManPage ./manpages/foo.1.gz",
   171  				Repository: config.RepoRef{
   172  					Owner: "foo",
   173  					Name:  "bar",
   174  				},
   175  			},
   176  		},
   177  		{
   178  			name: "zip",
   179  			nix: config.Nix{
   180  				Name:        "foozip",
   181  				IDs:         []string{"foo-zip"},
   182  				Description: "my test",
   183  				Homepage:    "https://goreleaser.com",
   184  				License:     "mit",
   185  				Repository: config.RepoRef{
   186  					Owner: "foo",
   187  					Name:  "bar",
   188  				},
   189  			},
   190  		},
   191  		{
   192  			name: "zip-with-dependencies",
   193  			nix: config.Nix{
   194  				Name:        "foozip",
   195  				IDs:         []string{"foo-zip"},
   196  				Description: "my test",
   197  				Homepage:    "https://goreleaser.com",
   198  				License:     "mit",
   199  				Dependencies: []config.NixDependency{
   200  					{Name: "git"},
   201  				},
   202  				Repository: config.RepoRef{
   203  					Owner: "foo",
   204  					Name:  "bar",
   205  				},
   206  			},
   207  		},
   208  		{
   209  			name:             "unibin",
   210  			expectRunErrorIs: ErrMultipleArchivesSamePlatform,
   211  			nix: config.Nix{
   212  				Name:        "unibin",
   213  				IDs:         []string{"unibin"},
   214  				Description: "my test",
   215  				Homepage:    "https://goreleaser.com",
   216  				License:     "mit",
   217  				Repository: config.RepoRef{
   218  					Owner: "foo",
   219  					Name:  "bar",
   220  				},
   221  			},
   222  		},
   223  		{
   224  			name: "no-archives",
   225  			expectRunErrorIs: errNoArchivesFound{
   226  				goamd64: "v2",
   227  				ids:     []string{"nopenopenope"},
   228  			},
   229  			nix: config.Nix{
   230  				Name:    "no-archives",
   231  				IDs:     []string{"nopenopenope"},
   232  				Goamd64: "v2",
   233  				Repository: config.RepoRef{
   234  					Owner: "foo",
   235  					Name:  "bar",
   236  				},
   237  			},
   238  		},
   239  		{
   240  			name: "unibin-replaces",
   241  			nix: config.Nix{
   242  				Name:        "unibin-replaces",
   243  				IDs:         []string{"unibin-replaces"},
   244  				Description: "my test",
   245  				Homepage:    "https://goreleaser.com",
   246  				License:     "mit",
   247  				Repository: config.RepoRef{
   248  					Owner: "foo",
   249  					Name:  "bar",
   250  				},
   251  			},
   252  		},
   253  		{
   254  			name: "partial",
   255  			nix: config.Nix{
   256  				Name: "partial",
   257  				IDs:  []string{"partial"},
   258  				Repository: config.RepoRef{
   259  					Owner: "foo",
   260  					Name:  "bar",
   261  				},
   262  			},
   263  		},
   264  		{
   265  			name:             "no-repo-name",
   266  			expectRunErrorIs: errNoRepoName,
   267  			nix: config.Nix{
   268  				Name: "doesnotmatter",
   269  				Repository: config.RepoRef{
   270  					Owner: "foo",
   271  				},
   272  			},
   273  		},
   274  		{
   275  			name:             "bad-name-tmpl",
   276  			expectRunErrorIs: &template.Error{},
   277  			nix: config.Nix{
   278  				Name: "{{ .Nope }}",
   279  				Repository: config.RepoRef{
   280  					Owner: "foo",
   281  					Name:  "bar",
   282  				},
   283  			},
   284  		},
   285  		{
   286  			name:             "bad-description-tmpl",
   287  			expectRunErrorIs: &template.Error{},
   288  			nix: config.Nix{
   289  				Description: "{{ .Nope }}",
   290  				Repository: config.RepoRef{
   291  					Owner: "foo",
   292  					Name:  "bar",
   293  				},
   294  			},
   295  		},
   296  		{
   297  			name:             "bad-homepage-tmpl",
   298  			expectRunErrorIs: &template.Error{},
   299  			nix: config.Nix{
   300  				Homepage: "{{ .Nope }}",
   301  				Repository: config.RepoRef{
   302  					Owner: "foo",
   303  					Name:  "bar",
   304  				},
   305  			},
   306  		},
   307  		{
   308  			name:             "bad-repo-tmpl",
   309  			expectRunErrorIs: &template.Error{},
   310  			nix: config.Nix{
   311  				Name: "doesnotmatter",
   312  				Repository: config.RepoRef{
   313  					Owner: "foo",
   314  					Name:  "{{ .Nope }}",
   315  				},
   316  			},
   317  		},
   318  		{
   319  			name:             "bad-skip-upload-tmpl",
   320  			expectRunErrorIs: &template.Error{},
   321  			nix: config.Nix{
   322  				Name:       "doesnotmatter",
   323  				SkipUpload: "{{ .Nope }}",
   324  				Repository: config.RepoRef{
   325  					Owner: "foo",
   326  					Name:  "bar",
   327  				},
   328  			},
   329  		},
   330  		{
   331  			name:             "bad-install-tmpl",
   332  			expectRunErrorIs: &template.Error{},
   333  			nix: config.Nix{
   334  				Name:    "foo",
   335  				Install: `{{.NoInstall}}`,
   336  				Repository: config.RepoRef{
   337  					Owner: "foo",
   338  					Name:  "bar",
   339  				},
   340  			},
   341  		},
   342  		{
   343  			name:             "bad-post-install-tmpl",
   344  			expectRunErrorIs: &template.Error{},
   345  			nix: config.Nix{
   346  				Name:        "foo",
   347  				PostInstall: `{{.NoPostInstall}}`,
   348  				Repository: config.RepoRef{
   349  					Owner: "foo",
   350  					Name:  "bar",
   351  				},
   352  			},
   353  		},
   354  		{
   355  			name:             "bad-path-tmpl",
   356  			expectRunErrorIs: &template.Error{},
   357  			nix: config.Nix{
   358  				Name: "foo",
   359  				Path: `{{.Foo}}/bar/foo.nix`,
   360  				Repository: config.RepoRef{
   361  					Owner: "foo",
   362  					Name:  "bar",
   363  				},
   364  			},
   365  		},
   366  		{
   367  			name:             "bad-release-url-tmpl",
   368  			expectRunErrorIs: &template.Error{},
   369  			nix: config.Nix{
   370  				Name:        "foo",
   371  				URLTemplate: "{{.BadURL}}",
   372  				Repository: config.RepoRef{
   373  					Owner: "foo",
   374  					Name:  "bar",
   375  				},
   376  			},
   377  		},
   378  		{
   379  			name:                 "skip-upload",
   380  			expectPublishErrorIs: errSkipUpload,
   381  			nix: config.Nix{
   382  				Name:       "doesnotmatter",
   383  				SkipUpload: "true",
   384  				IDs:        []string{"foo"},
   385  				Repository: config.RepoRef{
   386  					Owner: "foo",
   387  					Name:  "bar",
   388  				},
   389  			},
   390  		},
   391  		{
   392  			name:                 "skip-upload-auto",
   393  			expectPublishErrorIs: errSkipUploadAuto,
   394  			nix: config.Nix{
   395  				Name:       "doesnotmatter",
   396  				SkipUpload: "auto",
   397  				IDs:        []string{"foo"},
   398  				Repository: config.RepoRef{
   399  					Owner: "foo",
   400  					Name:  "bar",
   401  				},
   402  			},
   403  		},
   404  	} {
   405  		t.Run(tt.name, func(t *testing.T) {
   406  			folder := t.TempDir()
   407  			ctx := testctx.NewWithCfg(
   408  				config.Project{
   409  					Dist:        folder,
   410  					ProjectName: "foo",
   411  					Nix:         []config.Nix{tt.nix},
   412  				},
   413  				testctx.WithVersion("1.2.1"),
   414  				testctx.WithCurrentTag("v1.2.1"),
   415  				testctx.WithSemver(1, 2, 1, "rc1"),
   416  			)
   417  			createFakeArtifact := func(id, goos, goarch, goamd64, goarm, format string, extra map[string]any) {
   418  				if goarch != "arm" {
   419  					goarm = ""
   420  				}
   421  				if goarch != "amd64" {
   422  					goamd64 = ""
   423  				}
   424  				path := filepath.Join(folder, "dist/foo_"+goos+goarch+goamd64+goarm+"."+format)
   425  				art := artifact.Artifact{
   426  					Name:    "foo_" + goos + "_" + goarch + goamd64 + goarm + "." + format,
   427  					Path:    path,
   428  					Goos:    goos,
   429  					Goarch:  goarch,
   430  					Goarm:   goarm,
   431  					Goamd64: goamd64,
   432  					Type:    artifact.UploadableArchive,
   433  					Extra: map[string]interface{}{
   434  						artifact.ExtraID:        id,
   435  						artifact.ExtraFormat:    format,
   436  						artifact.ExtraBinaries:  []string{"foo"},
   437  						artifact.ExtraWrappedIn: "",
   438  					},
   439  				}
   440  				for k, v := range extra {
   441  					art.Extra[k] = v
   442  				}
   443  				ctx.Artifacts.Add(&art)
   444  
   445  				require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
   446  				f, err := os.Create(path)
   447  				require.NoError(t, err)
   448  				require.NoError(t, f.Close())
   449  			}
   450  
   451  			createFakeArtifact("unibin-replaces", "darwin", "all", "", "", "tar.gz", map[string]any{artifact.ExtraReplaces: true})
   452  			createFakeArtifact("unibin", "darwin", "all", "", "", "tar.gz", nil)
   453  			for _, goos := range []string{"linux", "darwin", "windows"} {
   454  				for _, goarch := range []string{"amd64", "arm64", "386", "arm"} {
   455  					if goos+goarch == "darwin386" {
   456  						continue
   457  					}
   458  					if goarch == "amd64" {
   459  						createFakeArtifact("partial", goos, goarch, "v1", "", "tar.gz", nil)
   460  						createFakeArtifact("foo", goos, goarch, "v1", "", "tar.gz", nil)
   461  						createFakeArtifact("unibin", goos, goarch, "v1", "", "tar.gz", nil)
   462  						if goos != "darwin" {
   463  							createFakeArtifact("unibin-replaces", goos, goarch, "v1", "", "tar.gz", nil)
   464  						}
   465  						createFakeArtifact("wrapped-in-dir", goos, goarch, "v1", "", "tar.gz", map[string]any{artifact.ExtraWrappedIn: "./foo"})
   466  						createFakeArtifact("foo-zip", goos, goarch, "v1", "", "zip", nil)
   467  						continue
   468  					}
   469  					if goarch == "arm" {
   470  						if goos != "linux" {
   471  							continue
   472  						}
   473  						createFakeArtifact("foo", goos, goarch, "", "6", "tar.gz", nil)
   474  						createFakeArtifact("foo", goos, goarch, "", "7", "tar.gz", nil)
   475  						createFakeArtifact("foo-zip", goos, goarch, "", "", "zip", nil)
   476  						createFakeArtifact("unibin-replaces", goos, goarch, "", "", "tar.gz", nil)
   477  						continue
   478  					}
   479  					createFakeArtifact("foo", goos, goarch, "", "", "tar.gz", nil)
   480  					createFakeArtifact("unibin", goos, goarch, "", "", "tar.gz", nil)
   481  					if goos != "darwin" {
   482  						createFakeArtifact("unibin-replaces", goos, goarch, "", "", "tar.gz", nil)
   483  					}
   484  					createFakeArtifact("wrapped-in-dir", goos, goarch, "", "", "tar.gz", map[string]any{artifact.ExtraWrappedIn: "./foo"})
   485  					createFakeArtifact("foo-zip", goos, goarch, "v1", "", "zip", nil)
   486  				}
   487  			}
   488  
   489  			client := client.NewMock()
   490  			bpipe := NewBuild()
   491  			ppipe := Pipe{
   492  				fakeNixShaPrefetcher{
   493  					"https://dummyhost/download/v1.2.1/foo_linux_amd64v1.tar.gz":  "sha1",
   494  					"https://dummyhost/download/v1.2.1/foo_linux_arm64.tar.gz":    "sha2",
   495  					"https://dummyhost/download/v1.2.1/foo_darwin_amd64v1.tar.gz": "sha3",
   496  					"https://dummyhost/download/v1.2.1/foo_darwin_arm64.tar.gz":   "sha4",
   497  					"https://dummyhost/download/v1.2.1/foo_darwin_all.tar.gz":     "sha5",
   498  					"https://dummyhost/download/v1.2.1/foo_linux_arm6.tar.gz":     "sha6",
   499  					"https://dummyhost/download/v1.2.1/foo_linux_arm7.tar.gz":     "sha7",
   500  					"https://dummyhost/download/v1.2.1/foo_linux_amd64v1.zip":     "sha8",
   501  					"https://dummyhost/download/v1.2.1/foo_linux_arm64.zip":       "sha9",
   502  					"https://dummyhost/download/v1.2.1/foo_darwin_amd64v1.zip":    "sha10",
   503  					"https://dummyhost/download/v1.2.1/foo_darwin_arm64.zip":      "sha11",
   504  					"https://dummyhost/download/v1.2.1/foo_darwin_all.zip":        "sha12",
   505  					"https://dummyhost/download/v1.2.1/foo_linux_arm6.zip":        "sha13",
   506  					"https://dummyhost/download/v1.2.1/foo_linux_arm7.zip":        "sha14",
   507  					"https://dummyhost/download/v1.2.1/foo_linux_386.zip":         "sha15",
   508  					"https://dummyhost/download/v1.2.1/foo_linux_386.tar.gz":      "sha16",
   509  				},
   510  			}
   511  
   512  			// default
   513  			require.NoError(t, bpipe.Default(ctx))
   514  
   515  			// run
   516  			if tt.expectRunErrorIs != nil {
   517  				err := bpipe.runAll(ctx, client)
   518  				require.ErrorAs(t, err, &tt.expectPublishErrorIs)
   519  				return
   520  			}
   521  			require.NoError(t, bpipe.runAll(ctx, client))
   522  			bts, err := os.ReadFile(ctx.Artifacts.Filter(artifact.ByType(artifact.Nixpkg)).Paths()[0])
   523  			require.NoError(t, err)
   524  			golden.RequireEqualExt(t, bts, "_build.nix")
   525  
   526  			// publish
   527  			if tt.expectPublishErrorIs != nil {
   528  				err := ppipe.publishAll(ctx, client)
   529  				require.ErrorAs(t, err, &tt.expectPublishErrorIs)
   530  				return
   531  			}
   532  			require.NoError(t, ppipe.publishAll(ctx, client))
   533  			require.True(t, client.CreatedFile)
   534  			golden.RequireEqualExt(t, []byte(client.Content), "_publish.nix")
   535  			require.NotContains(t, client.Content, strings.Repeat("0", 52))
   536  
   537  			if tt.nix.Repository.PullRequest.Enabled {
   538  				require.True(t, client.OpenedPullRequest)
   539  			}
   540  			if tt.nix.Path != "" {
   541  				require.Equal(t, tt.nix.Path, client.Path)
   542  			} else {
   543  				if tt.nix.Name == "" {
   544  					tt.nix.Name = "foo"
   545  				}
   546  				require.Equal(t, "pkgs/"+tt.nix.Name+"/default.nix", client.Path)
   547  			}
   548  		})
   549  	}
   550  }
   551  
   552  func TestErrNoArchivesFound(t *testing.T) {
   553  	require.EqualError(t, errNoArchivesFound{
   554  		goamd64: "v1",
   555  		ids:     []string{"foo", "bar"},
   556  	}, "no archives found matching goos=[darwin linux] goarch=[amd64 arm arm64 386] goarm=[6 7] goamd64=v1 ids=[foo bar]")
   557  }
   558  
   559  func TestDependencies(t *testing.T) {
   560  	require.Equal(t, []string{"nix-prefetch-url"}, Pipe{}.Dependencies(nil))
   561  }
   562  
   563  func TestBinInstallFormats(t *testing.T) {
   564  	t.Run("no-deps", func(t *testing.T) {
   565  		golden.RequireEqual(t, []byte(strings.Join(
   566  			binInstallFormats(config.Nix{}),
   567  			"\n",
   568  		)))
   569  	})
   570  	t.Run("deps", func(t *testing.T) {
   571  		golden.RequireEqual(t, []byte(strings.Join(
   572  			binInstallFormats(config.Nix{
   573  				Dependencies: []config.NixDependency{
   574  					{Name: "fish"},
   575  					{Name: "bash"},
   576  					{Name: "zsh"},
   577  				},
   578  			}),
   579  			"\n",
   580  		)))
   581  	})
   582  	t.Run("linux-only-deps", func(t *testing.T) {
   583  		golden.RequireEqual(t, []byte(strings.Join(
   584  			binInstallFormats(config.Nix{
   585  				Dependencies: []config.NixDependency{
   586  					linuxDep("foo"),
   587  					linuxDep("bar"),
   588  				},
   589  			}),
   590  			"\n",
   591  		)))
   592  	})
   593  	t.Run("darwin-only-deps", func(t *testing.T) {
   594  		golden.RequireEqual(t, []byte(strings.Join(
   595  			binInstallFormats(config.Nix{
   596  				Dependencies: []config.NixDependency{
   597  					darwinDep("foo"),
   598  					darwinDep("bar"),
   599  				},
   600  			}),
   601  			"\n",
   602  		)))
   603  	})
   604  	t.Run("mixed-deps", func(t *testing.T) {
   605  		golden.RequireEqual(t, []byte(strings.Join(
   606  			binInstallFormats(config.Nix{
   607  				Dependencies: []config.NixDependency{
   608  					{Name: "fish"},
   609  					linuxDep("foo"),
   610  					darwinDep("bar"),
   611  				},
   612  			}),
   613  			"\n",
   614  		)))
   615  	})
   616  }
   617  
   618  func darwinDep(s string) config.NixDependency {
   619  	return config.NixDependency{
   620  		Name: s,
   621  		OS:   "darwin",
   622  	}
   623  }
   624  
   625  func linuxDep(s string) config.NixDependency {
   626  	return config.NixDependency{
   627  		Name: s,
   628  		OS:   "linux",
   629  	}
   630  }
   631  
   632  type fakeNixShaPrefetcher map[string]string
   633  
   634  func (m fakeNixShaPrefetcher) Prefetch(url string) (string, error) {
   635  	return m[url], nil
   636  }
   637  func (m fakeNixShaPrefetcher) Available() bool { return true }