diff --git a/dirhash/hash.go b/dirhash/hash.go index 61d8face..62518915 100644 --- a/dirhash/hash.go +++ b/dirhash/hash.go @@ -1,103 +1,31 @@ -// Copyright 2018 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 dirhash defines hashes over directory trees. +// Package dirhash is a thin forwarding layer on top of +// [golang.org/x/mod/sumdb/dirhash]. See that package for documentation. +// +// Deprecated: use [golang.org/x/mod/sumdb/dirhash] instead. package dirhash import ( - "archive/zip" - "crypto/sha256" - "encoding/base64" - "errors" - "fmt" "io" - "os" - "path/filepath" - "sort" - "strings" + + "golang.org/x/mod/sumdb/dirhash" ) -var DefaultHash = Hash1 +var DefaultHash = dirhash.Hash1 -type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) +type Hash = dirhash.Hash func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { - h := sha256.New() - files = append([]string(nil), files...) - sort.Strings(files) - for _, file := range files { - if strings.Contains(file, "\n") { - return "", errors.New("filenames with newlines are not supported") - } - r, err := open(file) - if err != nil { - return "", err - } - hf := sha256.New() - _, err = io.Copy(hf, r) - r.Close() - if err != nil { - return "", err - } - fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) - } - return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil + return dirhash.Hash1(files, open) } func HashDir(dir, prefix string, hash Hash) (string, error) { - files, err := DirFiles(dir, prefix) - if err != nil { - return "", err - } - osOpen := func(name string) (io.ReadCloser, error) { - return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) - } - return hash(files, osOpen) + return dirhash.HashDir(dir, prefix, hash) } func DirFiles(dir, prefix string) ([]string, error) { - var files []string - dir = filepath.Clean(dir) - err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - rel := file - if dir != "." { - rel = file[len(dir)+1:] - } - f := filepath.Join(prefix, rel) - files = append(files, filepath.ToSlash(f)) - return nil - }) - if err != nil { - return nil, err - } - return files, nil + return dirhash.DirFiles(dir, prefix) } func HashZip(zipfile string, hash Hash) (string, error) { - z, err := zip.OpenReader(zipfile) - if err != nil { - return "", err - } - defer z.Close() - var files []string - zfiles := make(map[string]*zip.File) - for _, file := range z.File { - files = append(files, file.Name) - zfiles[file.Name] = file - } - zipOpen := func(name string) (io.ReadCloser, error) { - f := zfiles[name] - if f == nil { - return nil, fmt.Errorf("file %q not found in zip", name) // should never happen - } - return f.Open() - } - return hash(files, zipOpen) + return dirhash.HashZip(zipfile, hash) } diff --git a/dirhash/hash_test.go b/dirhash/hash_test.go deleted file mode 100644 index c4f7c227..00000000 --- a/dirhash/hash_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2018 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 dirhash - -import ( - "archive/zip" - "crypto/sha256" - "encoding/base64" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "testing" -) - -func h(s string) string { - return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) -} - -func htop(k string, s string) string { - sum := sha256.Sum256([]byte(s)) - return k + ":" + base64.StdEncoding.EncodeToString(sum[:]) -} - -func TestHash1(t *testing.T) { - files := []string{"xyz", "abc"} - open := func(name string) (io.ReadCloser, error) { - return io.NopCloser(strings.NewReader("data for " + name)), nil - } - want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "abc", h("data for xyz"), "xyz")) - out, err := Hash1(files, open) - if err != nil { - t.Fatal(err) - } - if out != want { - t.Errorf("Hash1(...) = %s, want %s", out, want) - } - - _, err = Hash1([]string{"xyz", "a\nbc"}, open) - if err == nil { - t.Error("Hash1: expected error on newline in filenames") - } -} - -func TestHashDir(t *testing.T) { - dir, err := os.MkdirTemp("", "dirhash-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - if err := os.WriteFile(filepath.Join(dir, "xyz"), []byte("data for xyz"), 0o666); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "abc"), []byte("data for abc"), 0o666); err != nil { - t.Fatal(err) - } - want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "prefix/abc", h("data for xyz"), "prefix/xyz")) - out, err := HashDir(dir, "prefix", Hash1) - if err != nil { - t.Fatalf("HashDir: %v", err) - } - if out != want { - t.Errorf("HashDir(...) = %s, want %s", out, want) - } -} - -func TestHashZip(t *testing.T) { - f, err := os.CreateTemp("", "dirhash-test-") - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) - defer f.Close() - - z := zip.NewWriter(f) - w, err := z.Create("prefix/xyz") - if err != nil { - t.Fatal(err) - } - w.Write([]byte("data for xyz")) - w, err = z.Create("prefix/abc") - if err != nil { - t.Fatal(err) - } - w.Write([]byte("data for abc")) - if err := z.Close(); err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - - want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "prefix/abc", h("data for xyz"), "prefix/xyz")) - out, err := HashZip(f.Name(), Hash1) - if err != nil { - t.Fatalf("HashDir: %v", err) - } - if out != want { - t.Errorf("HashDir(...) = %s, want %s", out, want) - } -} - -func TestDirFiles(t *testing.T) { - dir, err := os.MkdirTemp("", "dirfiles-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - if err := os.WriteFile(filepath.Join(dir, "xyz"), []byte("data for xyz"), 0o666); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "abc"), []byte("data for abc"), 0o666); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(dir, "subdir"), 0o777); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "subdir", "xyz"), []byte("data for subdir xyz"), 0o666); err != nil { - t.Fatal(err) - } - prefix := "foo/bar@v2.3.4" - out, err := DirFiles(dir, prefix) - if err != nil { - t.Fatalf("DirFiles: %v", err) - } - for _, file := range out { - if !strings.HasPrefix(file, prefix) { - t.Errorf("Dir file = %s, want prefix %s", file, prefix) - } - } -} diff --git a/goproxytest/proxy.go b/goproxytest/proxy.go index 070c7ee2..d3fc975a 100644 --- a/goproxytest/proxy.go +++ b/goproxytest/proxy.go @@ -33,6 +33,7 @@ import ( "os" "path/filepath" "strings" + "testing" "golang.org/x/mod/module" "golang.org/x/mod/semver" @@ -45,26 +46,43 @@ type Server struct { server *http.Server URL string dir string + logf func(string, ...any) modList []module.Version zipCache par.Cache archiveCache par.Cache } -// StartProxy starts the Go module proxy listening on the given +// NewTestServer is a wrapper around [NewServer] for use in Go tests. +// Failure to start the server stops the test via [testing.TB.Fatalf], +// all server logs go through [testing.TB.Logf], +// and the server is closed when the test finishes via [testing.TB.Cleanup]. +func NewTestServer(tb testing.TB, dir, addr string) *Server { + srv, err := newServer(dir, addr, tb.Logf) + if err != nil { + tb.Fatalf("cannot start Go proxy: %v", err) + } + tb.Cleanup(srv.Close) + return srv +} + +// NewServer starts the Go module proxy listening on the given // network address. It serves modules taken from the given directory // name. If addr is empty, it will listen on an arbitrary // localhost port. If dir is empty, "testmod" will be used. // // The returned Server should be closed after use. func NewServer(dir, addr string) (*Server, error) { - var srv Server + return newServer(dir, addr, log.Printf) +} + +func newServer(dir, addr string, logf func(string, ...any)) (*Server, error) { if addr == "" { addr = "localhost:0" } if dir == "" { dir = "testmod" } - srv.dir = dir + srv := Server{dir: dir, logf: logf} if err := srv.readModList(); err != nil { return nil, fmt.Errorf("cannot read modules: %v", err) } @@ -79,7 +97,7 @@ func NewServer(dir, addr string) (*Server, error) { srv.URL = "http://" + addr + "/mod" go func() { if err := srv.server.Serve(l); err != nil && err != http.ErrServerClosed { - log.Printf("go proxy: http.Serve: %v", err) + srv.logf("go proxy: http.Serve: %v", err) } }() return &srv, nil @@ -141,7 +159,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) { enc, file := path[:i], path[i+len("/@v/"):] path, err := module.UnescapePath(enc) if err != nil { - fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err) + srv.logf("go proxy_test: %v\n", err) http.NotFound(w, r) return } @@ -169,7 +187,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) { encVers, ext := file[:i], file[i+1:] vers, err := module.UnescapeVersion(encVers) if err != nil { - fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err) + srv.logf("go proxy_test: %v\n", err) http.NotFound(w, r) return } @@ -204,7 +222,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) { // to resolve github.com, github.com/hello and github.com/hello/world. // cmd/go expects a 404/410 response if there is nothing there. Hence we // cannot return with a 500. - fmt.Fprintf(os.Stderr, "go proxy: no archive %s %s\n", path, vers) + srv.logf("go proxy: no archive %s %s\n", path, vers) http.NotFound(w, r) return } @@ -246,7 +264,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) { }).(cached) if c.err != nil { - fmt.Fprintf(os.Stderr, "go proxy: %v\n", c.err) + srv.logf("go proxy: %v\n", c.err) http.Error(w, c.err.Error(), 500) return } @@ -277,12 +295,12 @@ func (srv *Server) findHash(m module.Version) string { func (srv *Server) readArchive(path, vers string) *txtar.Archive { enc, err := module.EscapePath(path) if err != nil { - fmt.Fprintf(os.Stderr, "go proxy: %v\n", err) + srv.logf("go proxy: %v\n", err) return nil } encVers, err := module.EscapeVersion(vers) if err != nil { - fmt.Fprintf(os.Stderr, "go proxy: %v\n", err) + srv.logf("go proxy: %v\n", err) return nil } @@ -324,7 +342,7 @@ func (srv *Server) readArchive(path, vers string) *txtar.Archive { } if err != nil { if !os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "go proxy: %v\n", err) + srv.logf("go proxy: %v\n", err) } a = nil } diff --git a/goproxytest/proxy_test.go b/goproxytest/proxy_test.go index 8f9e3e56..35a9452d 100644 --- a/goproxytest/proxy_test.go +++ b/goproxytest/proxy_test.go @@ -14,10 +14,7 @@ import ( ) func TestScripts(t *testing.T) { - srv, err := goproxytest.NewServer(filepath.Join("testdata", "mod"), "") - if err != nil { - t.Fatalf("cannot start proxy: %v", err) - } + srv := goproxytest.NewTestServer(t, filepath.Join("testdata", "mod"), "") p := testscript.Params{ Dir: "testdata", Setup: func(e *testscript.Env) error {