Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: rogpeppe/go-internal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.10.0
Choose a base ref
...
head repository: rogpeppe/go-internal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.12.0
Choose a head ref

Commits on Mar 27, 2023

  1. modfile: forward to x/mod (#208)

    The canonical implementation of this code has now been made available
    inside the x/mod module, so use that. The API is almost identical,
    with the exception of `ParseGopkgIn` which isn't there.
    rogpeppe authored Mar 27, 2023
    Copy the full SHA
    6e5fb8c View commit details
  2. semver: forward to x/mod/semver (#210)

    This is now the canonical location of the semver package.
    rogpeppe authored Mar 27, 2023
    Copy the full SHA
    a354da8 View commit details
  3. txtar: alias types to x/tools (#209)

    Although we can't quite use the x/tools/txtar implementation in its
    entirety (golang/go#59264 needs
    to be fixed first, and we implement some other functions that
    x/tools/txtar doesn't (yet?) have, we can at least alias its
    types, which makes it possible to work with other packages
    that use those types.
    rogpeppe authored Mar 27, 2023
    Copy the full SHA
    ca7ccbd View commit details
  4. module: forward to x/mod/module (#211)

    The golang.org/x/mod/module is now the canonical
    location of this package.
    rogpeppe authored Mar 27, 2023
    Copy the full SHA
    d4265f6 View commit details
  5. all: deprecate wrapper packages

    People should in general be using the upstream packages
    rather than these. Also use the upstream packages directly
    when possible and remove some tests that aren't testing
    code in this module any more.
    rogpeppe authored and mvdan committed Mar 27, 2023
    Copy the full SHA
    81831f2 View commit details

Commits on Apr 27, 2023

  1. testscript: expose (*TestScript).stdout via Stdout() (#216)

    Similarly, expose (*TestScript).stderr via Stderr().
    
    Closes #139
    myitcv authored Apr 27, 2023
    Copy the full SHA
    22b9127 View commit details

Commits on May 5, 2023

  1. testscript: use unix.CloneFile on MacOs

    To fix unexpected errors of type:
    
    ```
    [signal: killed]
    FAIL: testscripts/myecho.txt:1: unexpected command failure
    ```
    
    Fixes #200
    bep authored and mvdan committed May 5, 2023
    Copy the full SHA
    44c3b86 View commit details
  2. testscript: suggest misspelled commands

    If a command is not found, we go through the list of defined commands
    and check if any of them are sufficiently close to the one used.
    "Sufficiently close" is defined by having a Damerau-Levenshtein distance
    of 1, which feels like it hits the sweet spot between usefulness and
    ease of implementation.
    
    The negation case is still special-cased, as negation is not in the set
    of defined commands.
    
    Fixes #190
    Merovius authored and mvdan committed May 5, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    andrzej-stencel Andrzej Stencel
    Copy the full SHA
    5150104 View commit details

Commits on May 6, 2023

  1. CI: go back to macos-latest

    Now that it seems we found a fix to #200, there is no reason to stick
    to macos-11, which will likely be deprecated soon.
    
    Update actions/setup-go to its latest version as well.
    The new version uses caching by default, which we do not need.
    
    While here, tidy up the cloneFile docs a bit.
    mvdan committed May 6, 2023
    Copy the full SHA
    b93e002 View commit details

Commits on May 8, 2023

  1. internal: remove unused packages

    internal/syscall/windows/registry was never imported by any of our
    packages, so it seems to always have been unneeded module zip bloat.
    
    internal/textutil was used by modfile, which now simply forwards to
    x/mod/modfile, so the import is gone.
    mvdan committed May 8, 2023
    Copy the full SHA
    a4f6fab View commit details

Commits on May 15, 2023

  1. lockedfile: update to Go tip as of March 2023

    As of commit 7a21f799a5ac23d3e191a106d71af9b8f65279fd,
    which is crucially right before https://go.dev/cl/476917,
    as then internal/filelock starts using Go 1.21's errors.ErrUnsupported.
    We still want to support Go 1.19 and 1.20 for a while.
    
    The only change besides the import path rewriting is to drop testenv,
    which was only used for MustHaveExec and Command.
    
    Note that we no longer need to worry about unix build tags,
    as we now require Go 1.19 or later.
    mvdan committed May 15, 2023
    Copy the full SHA
    eeed7e8 View commit details
  2. cache: update to Go tip as of April 2023

    As of commit 0fd6ae548f550bdbee4a434285ff052fb9dc7417.
    
    Besides rewriting import paths, we swapped base.Fatalf with log.Fatalf,
    and replaced cfg.Getenv with os.Getenv, adding a note about the
    difference in behavior. The old code already had this limitation.
    
    We hadn't updated this package since it was first copied in 2018,
    so quite a few changes have taken place.
    Of note, it now supports mmap; leave that out for now, to keep this
    commit simple and to leave adding the mmap package for another patch.
    
    A minor API change is that Trim now returns an error.
    While technically a breaking change, the vast majority of users will be
    simply calling the API without expecting a result, and that will
    continue to work like it did before.
    Checking for errors on trim is useful, which is why upstream added it.
    
    Finally, the cache now uses lockedfile, which we already copied over.
    mvdan committed May 15, 2023
    Copy the full SHA
    5821053 View commit details
  3. all: remove some unused code

    Primarily testscript's code to support calling testing.MainStart;
    we originally needed that to implement our own deep code coverage,
    but thankfully `go test` does that for us automatically now.
    mvdan committed May 15, 2023
    Copy the full SHA
    bc1bde8 View commit details

Commits on May 23, 2023

  1. Copy the full SHA
    2d7bba0 View commit details

Commits on May 24, 2023

  1. README: add context on maintainers and a testscript overview with lin…

    …ks (#225)
    
    Help give some context for people who might be evaluating using this repo and are wondering:
    
    * what is the bus factor for the repo?
    * who are the maintainers?
    * are the maintainer(s) of this repo still interested in this repo (including, are they still using it)?
    * are other people using testscript?
    * where did testscript come from?
    
    Obviously, someone can hunt around to determine many of these things on their own,
    but the intent is to help make that process more efficient.
    
    Updates #196.
    thepudds authored May 24, 2023
    Copy the full SHA
    ec11942 View commit details

Commits on Aug 7, 2023

  1. testscript: add ttyin/ttyout commands

    FiloSottile authored and mvdan committed Aug 7, 2023
    Copy the full SHA
    e748a67 View commit details
  2. Copy the full SHA
    3fbe0b6 View commit details

Commits on Sep 26, 2023

  1. all: add Go 1.21, drop Go 1.19

    And fix up the tests and code to adapt accordingly.
    While here, update the checkout action as well.
    mvdan committed Sep 26, 2023
    Copy the full SHA
    b6a9d8b View commit details
  2. testscript: propagate GORACE like we already do with GOCOVERDIR

    Do both in a loop to deduplicate code.
    While here, only set them if they aren't empty;
    this way we don't unnecessarily pollute Vars with entries
    such as `GOCOVERDIR=` when they don't do anything useful.
    mvdan committed Sep 26, 2023
    Copy the full SHA
    32ae337 View commit details

Commits on Oct 26, 2023

  1. testscript,goproxytest: use filepath.WalkDir

    This can easily save hundreds of stat calls per test script,
    particularly when testing Go tools.
    
    While here, remove a few uses of the deprecated io/ioutil in cmd,
    and check some missed errors in txtar-c.
    mvdan committed Oct 26, 2023
    Copy the full SHA
    0bcf77f View commit details
  2. testscript: add TestScript.Name

    This will be useful in some cmd/cue test scripts
    where we want each test to create a unique remote resource
    that includes the current test name as a prefix,
    for the sake of more easily seeing which test created which resource.
    mvdan committed Oct 26, 2023
    Copy the full SHA
    fa6a31e View commit details

Commits on Dec 13, 2023

  1. robustio: copy from cmd/go/internal/robustio (#239)

    Copied from Go commit b18b05881691861c4279a50010829150f1684fa9.
    rogpeppe authored Dec 13, 2023
    Copy the full SHA
    2c88e7f View commit details
Showing with 1,511 additions and 5,152 deletions.
  1. +6 −5 .github/workflows/test.yml
  2. +27 −2 README.md
  3. +138 −41 cache/cache.go
  4. +30 −64 cache/cache_test.go
  5. +45 −41 cache/default.go
  6. +0 −68 cache/default_unix_test.go
  7. +17 −1 cache/hash.go
  8. +1 −2 cache/hash_test.go
  9. +3 −1 cmd/testscript/testdata/noproxy.txt
  10. +13 −11 cmd/txtar-addmod/addmod.go
  11. +10 −6 cmd/txtar-c/savedir.go
  12. +3 −3 cmd/txtar-x/extract.go
  13. +2 −3 cmd/txtar-x/extract_test.go
  14. +1 −1 diff/diff_test.go
  15. +7 −1 go.mod
  16. +6 −0 go.sum
  17. +21 −20 goproxytest/proxy.go
  18. +1 −1 goproxytest/pseudo.go
  19. +0 −9 gotooltest/setup.go
  20. +1 −4 gotooltest/testdata/cover.txt
  21. +68 −0 internal/misspell/misspell.go
  22. +100 −0 internal/misspell/misspell_test.go
  23. +3 −0 internal/misspell/testdata/fuzz/FuzzAlmostEqual/295b316649ae86dd
  24. +3 −0 internal/misspell/testdata/fuzz/FuzzAlmostEqual/5bd9cd4e8c887808
  25. +3 −0 internal/misspell/testdata/fuzz/FuzzAlmostEqual/b323cef1fc26e507
  26. +3 −0 internal/misspell/testdata/fuzz/FuzzAlmostEqual/c6edde4256d6f5eb
  27. +0 −1 internal/os/execpath/lp_js.go
  28. +0 −1 internal/os/execpath/lp_unix.go
  29. +0 −173 internal/syscall/windows/registry/key.go
  30. +0 −7 internal/syscall/windows/registry/mksyscall.go
  31. +0 −32 internal/syscall/windows/registry/syscall.go
  32. +0 −385 internal/syscall/windows/registry/value.go
  33. +0 −111 internal/syscall/windows/registry/zsyscall_windows.go
  34. +0 −78 internal/textutil/diff.go
  35. +0 −45 internal/textutil/diff_test.go
  36. +0 −9 internal/textutil/doc.go
  37. +0 −1 lockedfile/internal/filelock/filelock_fcntl.go
  38. +1 −2 lockedfile/internal/filelock/filelock_other.go
  39. +0 −37 lockedfile/internal/filelock/filelock_plan9.go
  40. +0 −1 lockedfile/internal/filelock/filelock_test.go
  41. +0 −1 lockedfile/internal/filelock/filelock_unix.go
  42. +0 −1 lockedfile/internal/filelock/filelock_windows.go
  43. +24 −9 lockedfile/lockedfile_test.go
  44. +59 −0 modfile/forward.go
  45. +0 −164 modfile/print.go
  46. +0 −868 modfile/read.go
  47. +0 −334 modfile/read_test.go
  48. +0 −103 modfile/replace_test.go
  49. +0 −724 modfile/rule.go
  50. +0 −90 modfile/rule_test.go
  51. +0 −29 modfile/testdata/block.golden
  52. +0 −29 modfile/testdata/block.in
  53. +0 −10 modfile/testdata/comment.golden
  54. +0 −8 modfile/testdata/comment.in
  55. 0 modfile/testdata/empty.golden
  56. 0 modfile/testdata/empty.in
  57. +0 −6 modfile/testdata/gopkg.in.golden
  58. +0 −1 modfile/testdata/module.golden
  59. +0 −1 modfile/testdata/module.in
  60. +0 −5 modfile/testdata/replace.golden
  61. +0 −5 modfile/testdata/replace.in
  62. +0 −10 modfile/testdata/replace2.golden
  63. +0 −10 modfile/testdata/replace2.in
  64. +0 −7 modfile/testdata/rule1.golden
  65. +61 −0 module/forward.go
  66. +0 −540 module/module.go
  67. +0 −318 module/module_test.go
  68. +2 −0 renameio/renameio.go
  69. +53 −0 robustio/robustio.go
  70. +21 −0 robustio/robustio_darwin.go
  71. +91 −0 robustio/robustio_flaky.go
  72. +27 −0 robustio/robustio_other.go
  73. +28 −0 robustio/robustio_windows.go
  74. +39 −0 semver/forward.go
  75. +0 −388 semver/semver.go
  76. +0 −182 semver/semver_test.go
  77. +0 −1 testenv/testenv_cgo.go
  78. +0 −1 testenv/testenv_notwin.go
  79. +10 −0 testscript/clonefile.go
  80. +8 −0 testscript/clonefile_darwin.go
  81. +12 −0 testscript/clonefile_other.go
  82. +42 −0 testscript/cmd.go
  83. +11 −1 testscript/doc.go
  84. +0 −8 testscript/envvarname.go
  85. +0 −7 testscript/envvarname_windows.go
  86. +4 −41 testscript/exe.go
  87. +0 −48 testscript/exe_go118.go
  88. +62 −0 testscript/internal/pty/pty.go
  89. +32 −0 testscript/internal/pty/pty_darwin.go
  90. +26 −0 testscript/internal/pty/pty_linux.go
  91. +21 −0 testscript/internal/pty/pty_unsupported.go
  92. +39 −0 testscript/testdata/cmd_stdout_stderr.txt
  93. +11 −0 testscript/testdata/pty.txt
  94. +17 −0 testscript/testdata/testscript_notfound.txt
  95. +20 −0 testscript/testdata/testscript_stdout_stderr_error.txt
  96. +216 −14 testscript/testscript.go
  97. +53 −5 testscript/testscript_test.go
  98. +8 −15 txtar/archive.go
  99. +1 −1 txtar/archive_test.go
11 changes: 6 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -12,27 +12,28 @@ jobs:
fail-fast: false
matrix:
go-version:
- '1.19.x'
- '1.20.x'
- '1.21.x'
os:
- ubuntu-latest
- macos-11
- macos-latest
- windows-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
cache: false # our tests are quick enough
- name: Test
run: |
go test ./...
go test -race ./...
- name: Tidy
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.20.x' # no need to do this everywhere
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.21.x' # no need to do this everywhere
run: |
go mod tidy
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
This repository factors out an opinionated selection of internal packages and functionality from the Go standard
library. Currently this consists mostly of packages and testing code from within the Go tool implementation.

This repo is [primarily maintained](https://github.com/rogpeppe/go-internal/graphs/contributors) by long-time
[Go contributors](https://github.com/golang/go/contributors) who are also currently
[maintaining CUE](https://github.com/cue-lang/cue/graphs/contributors) (which is primarily written in Go
and which relies upon several of the packages here).

Contributions are welcome, but please open an issue for discussion first.

## Packages

Included are the following:

- dirhash: calculate hashes over directory trees the same way that the Go tool does.
@@ -15,6 +24,22 @@ Included are the following:
- testscript: script-based testing based on txtar files
- txtar: simple text-based file archives for testing.

# Links
### testscript

- [Test scripts in Go](https://bitfieldconsulting.com/golang/test-scripts)
The most popular package here is the [testscript](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript) package:
* Provides a shell-like test environment that is very nicely tuned for testing Go CLI commands.
* Extracted from the core Go team's internal testscript package ([cmd/go/internal/script](https://github.com/golang/go/tree/master/src/cmd/go/internal/script)),
which is [heavily used](https://github.com/golang/go/tree/master/src/cmd/go/testdata/script) to test the `go` command.
* Supports patterns for checking stderr/stdout, command pass/fail assertions, and so on.
* Integrates well with `go test`, including coverage support.
* Inputs and sample output files can use the simple [txtar](https://pkg.go.dev/golang.org/x/tools/txtar)
text archive format, also used by the Go playground.
* Allows [automatically updating](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript#Params)
golden files.
* Built-in support for Go concepts like build tags.
* Accompanied by a [testscript](https://github.com/rogpeppe/go-internal/tree/master/cmd/testscript) command
for running standalone scripts with files embedded in txtar format.

A nice introduction to using testscripts is this [blog post](https://bitfieldconsulting.com/golang/test-scripts) series.
Both testscript and txtar were [originally created](https://github.com/golang/go/commit/5890e25b7ccb2d2249b2f8a02ef5dbc36047868b)
by Russ Cox.
179 changes: 138 additions & 41 deletions cache/cache.go
Original file line number Diff line number Diff line change
@@ -12,12 +12,14 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/rogpeppe/go-internal/lockedfile"
)

// An ActionID is a cache action key, the hash of a complete description of a
@@ -31,7 +33,6 @@ type OutputID [HashSize]byte
// A Cache is a package cache, backed by a file system directory tree.
type Cache struct {
dir string
log *os.File
now func() time.Time
}

@@ -52,21 +53,16 @@ func Open(dir string) (*Cache, error) {
return nil, err
}
if !info.IsDir() {
return nil, &os.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
return nil, &fs.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
}
for i := 0; i < 256; i++ {
name := filepath.Join(dir, fmt.Sprintf("%02x", i))
if err := os.MkdirAll(name, 0o777); err != nil {
if err := os.MkdirAll(name, 0777); err != nil {
return nil, err
}
}
f, err := os.OpenFile(filepath.Join(dir, "log.txt"), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
if err != nil {
return nil, err
}
c := &Cache{
dir: dir,
log: f,
now: time.Now,
}
return c, nil
@@ -77,7 +73,22 @@ func (c *Cache) fileName(id [HashSize]byte, key string) string {
return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
}

var errMissing = errors.New("cache entry not found")
// An entryNotFoundError indicates that a cache entry was not found, with an
// optional underlying reason.
type entryNotFoundError struct {
Err error
}

func (e *entryNotFoundError) Error() string {
if e.Err == nil {
return "cache entry not found"
}
return fmt.Sprintf("cache entry not found: %v", e.Err)
}

func (e *entryNotFoundError) Unwrap() error {
return e.Err
}

const (
// action entry file is "v1 <hex id> <hex out> <decimal size space-padded to 20 bytes> <unixnano space-padded to 20 bytes>\n"
@@ -96,6 +107,8 @@ const (
// GODEBUG=gocacheverify=1.
var verify = false

var errVerifyMode = errors.New("gocacheverify=1")

// DebugTest is set when GODEBUG=gocachetest=1 is in the environment.
var DebugTest = false

@@ -124,7 +137,7 @@ func initEnv() {
// saved file for that output ID is still available.
func (c *Cache) Get(id ActionID) (Entry, error) {
if verify {
return Entry{}, errMissing
return Entry{}, &entryNotFoundError{Err: errVerifyMode}
}
return c.get(id)
}
@@ -137,52 +150,62 @@ type Entry struct {

// get is Get but does not respect verify mode, so that Put can use it.
func (c *Cache) get(id ActionID) (Entry, error) {
missing := func() (Entry, error) {
fmt.Fprintf(c.log, "%d miss %x\n", c.now().Unix(), id)
return Entry{}, errMissing
missing := func(reason error) (Entry, error) {
return Entry{}, &entryNotFoundError{Err: reason}
}
f, err := os.Open(c.fileName(id, "a"))
if err != nil {
return missing()
return missing(err)
}
defer f.Close()
entry := make([]byte, entrySize+1) // +1 to detect whether f is too long
if n, err := io.ReadFull(f, entry); n != entrySize || err != io.ErrUnexpectedEOF {
return missing()
if n, err := io.ReadFull(f, entry); n > entrySize {
return missing(errors.New("too long"))
} else if err != io.ErrUnexpectedEOF {
if err == io.EOF {
return missing(errors.New("file is empty"))
}
return missing(err)
} else if n < entrySize {
return missing(errors.New("entry file incomplete"))
}
if entry[0] != 'v' || entry[1] != '1' || entry[2] != ' ' || entry[3+hexSize] != ' ' || entry[3+hexSize+1+hexSize] != ' ' || entry[3+hexSize+1+hexSize+1+20] != ' ' || entry[entrySize-1] != '\n' {
return missing()
return missing(errors.New("invalid header"))
}
eid, entry := entry[3:3+hexSize], entry[3+hexSize:]
eout, entry := entry[1:1+hexSize], entry[1+hexSize:]
esize, entry := entry[1:1+20], entry[1+20:]
etime, entry := entry[1:1+20], entry[1+20:]
var buf [HashSize]byte
if _, err := hex.Decode(buf[:], eid); err != nil || buf != id {
return missing()
if _, err := hex.Decode(buf[:], eid); err != nil {
return missing(fmt.Errorf("decoding ID: %v", err))
} else if buf != id {
return missing(errors.New("mismatched ID"))
}
if _, err := hex.Decode(buf[:], eout); err != nil {
return missing()
return missing(fmt.Errorf("decoding output ID: %v", err))
}
i := 0
for i < len(esize) && esize[i] == ' ' {
i++
}
size, err := strconv.ParseInt(string(esize[i:]), 10, 64)
if err != nil || size < 0 {
return missing()
if err != nil {
return missing(fmt.Errorf("parsing size: %v", err))
} else if size < 0 {
return missing(errors.New("negative size"))
}
i = 0
for i < len(etime) && etime[i] == ' ' {
i++
}
tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
if err != nil || size < 0 {
return missing()
if err != nil {
return missing(fmt.Errorf("parsing timestamp: %v", err))
} else if tm < 0 {
return missing(errors.New("negative timestamp"))
}

fmt.Fprintf(c.log, "%d get %x\n", c.now().Unix(), id)

c.used(c.fileName(id, "a"))

return Entry{buf, size, time.Unix(0, tm)}, nil
@@ -197,8 +220,11 @@ func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
}
file = c.OutputFile(entry.OutputID)
info, err := os.Stat(file)
if err != nil || info.Size() != entry.Size {
return "", Entry{}, errMissing
if err != nil {
return "", Entry{}, &entryNotFoundError{Err: err}
}
if info.Size() != entry.Size {
return "", Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
}
return file, entry, nil
}
@@ -211,13 +237,35 @@ func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
if err != nil {
return nil, entry, err
}
data, _ := ioutil.ReadFile(c.OutputFile(entry.OutputID))
data, _ := os.ReadFile(c.OutputFile(entry.OutputID))
if sha256.Sum256(data) != entry.OutputID {
return nil, entry, errMissing
return nil, entry, &entryNotFoundError{Err: errors.New("bad checksum")}
}
return data, entry, nil
}

/*
TODO: consider copying cmd/go/internal/mmap over for this method
// GetMmap looks up the action ID in the cache and returns
// the corresponding output bytes.
// GetMmap should only be used for data that can be expected to fit in memory.
func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
entry, err := c.Get(id)
if err != nil {
return nil, entry, err
}
md, err := mmap.Mmap(c.OutputFile(entry.OutputID))
if err != nil {
return nil, Entry{}, err
}
if int64(len(md.Data)) != entry.Size {
return nil, Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
}
return md.Data, entry, nil
}
*/

// OutputFile returns the name of the cache file storing output with the given OutputID.
func (c *Cache) OutputFile(out OutputID) string {
file := c.fileName(out, "d")
@@ -261,16 +309,23 @@ func (c *Cache) used(file string) {
}

// Trim removes old cache entries that are likely not to be reused.
func (c *Cache) Trim() {
func (c *Cache) Trim() error {
now := c.now()

// We maintain in dir/trim.txt the time of the last completed cache trim.
// If the cache has been trimmed recently enough, do nothing.
// This is the common case.
data, _ := ioutil.ReadFile(filepath.Join(c.dir, "trim.txt"))
t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval {
return
// If the trim file is corrupt, detected if the file can't be parsed, or the
// trim time is too far in the future, attempt the trim anyway. It's possible that
// the cache was full when the corruption happened. Attempting a trim on
// an empty cache is cheap, so there wouldn't be a big performance hit in that case.
if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil {
if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil {
lastTrim := time.Unix(t, 0)
if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval {
return nil
}
}
}

// Trim each of the 256 subdirectories.
@@ -282,7 +337,15 @@ func (c *Cache) Trim() {
c.trimSubdir(subdir, cutoff)
}

ioutil.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())), 0o666)
// Ignore errors from here: if we don't write the complete timestamp, the
// cache will appear older than it is, and we'll trim it again next time.
var b bytes.Buffer
fmt.Fprintf(&b, "%d", now.Unix())
if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0666); err != nil {
return err
}

return nil
}

// trimSubdir trims a single cache subdirectory.
@@ -326,7 +389,7 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
// in verify mode we are double-checking that the cache entries
// are entirely reproducible. As just noted, this may be unrealistic
// in some cases but the check is also useful for shaking out real bugs.
entry := []byte(fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano()))
entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
if verify && allowVerify {
old, err := c.get(id)
if err == nil && (old.OutputID != out || old.Size != size) {
@@ -336,13 +399,35 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
}
}
file := c.fileName(id, "a")
if err := ioutil.WriteFile(file, entry, 0o666); err != nil {

// Copy file to cache directory.
mode := os.O_WRONLY | os.O_CREATE
f, err := os.OpenFile(file, mode, 0666)
if err != nil {
return err
}
_, err = f.WriteString(entry)
if err == nil {
// Truncate the file only *after* writing it.
// (This should be a no-op, but truncate just in case of previous corruption.)
//
// This differs from os.WriteFile, which truncates to 0 *before* writing
// via os.O_TRUNC. Truncating only after writing ensures that a second write
// of the same content to the same file is idempotent, and does not — even
// temporarily! — undo the effect of the first write.
err = f.Truncate(int64(len(entry)))
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
// TODO(bcmills): This Remove potentially races with another go command writing to file.
// Can we eliminate it?
os.Remove(file)
return err
}
os.Chtimes(file, c.now(), c.now()) // mainly for tests

fmt.Fprintf(c.log, "%d put %x %x %d\n", c.now().Unix(), id, out, size)
return nil
}

@@ -413,7 +498,7 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
if err == nil && info.Size() > size { // shouldn't happen but fix in case
mode |= os.O_TRUNC
}
f, err := os.OpenFile(name, mode, 0o666)
f, err := os.OpenFile(name, mode, 0666)
if err != nil {
return err
}
@@ -471,3 +556,15 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {

return nil
}

// FuzzDir returns a subdirectory within the cache for storing fuzzing data.
// The subdirectory may not exist.
//
// This directory is managed by the internal/fuzz package. Files in this
// directory aren't removed by the 'go clean -cache' command or by Trim.
// They may be removed with 'go clean -fuzzcache'.
//
// TODO(#48526): make Trim remove unused files from this directory.
func (c *Cache) FuzzDir() string {
return filepath.Join(c.dir, "fuzz")
}
94 changes: 30 additions & 64 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -20,7 +19,7 @@ func init() {
}

func TestBasic(t *testing.T) {
dir, err := ioutil.TempDir("", "cachetest-")
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
@@ -31,7 +30,7 @@ func TestBasic(t *testing.T) {
}

cdir := filepath.Join(dir, "c1")
if err := os.Mkdir(cdir, 0o777); err != nil {
if err := os.Mkdir(cdir, 0777); err != nil {
t.Fatal(err)
}

@@ -65,7 +64,7 @@ func TestBasic(t *testing.T) {
}

func TestGrowth(t *testing.T) {
dir, err := ioutil.TempDir("", "cachetest-")
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
@@ -78,7 +77,7 @@ func TestGrowth(t *testing.T) {

n := 10000
if testing.Short() {
n = 1000
n = 10
}

for i := 0; i < n; i++ {
@@ -118,7 +117,7 @@ func TestVerifyPanic(t *testing.T) {
t.Fatal("initEnv did not set verify")
}

dir, err := ioutil.TempDir("", "cachetest-")
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
@@ -144,63 +143,14 @@ func TestVerifyPanic(t *testing.T) {
t.Fatal("mismatched Put did not panic in verify mode")
}

func TestCacheLog(t *testing.T) {
dir, err := ioutil.TempDir("", "cachetest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)

c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
c.now = func() time.Time { return time.Unix(1e9, 0) }

id := ActionID(dummyID(1))
c.Get(id)
c.PutBytes(id, []byte("abc"))
c.Get(id)

c, err = Open(dir)
if err != nil {
t.Fatalf("Open #2: %v", err)
}
c.now = func() time.Time { return time.Unix(1e9+1, 0) }
c.Get(id)

id2 := ActionID(dummyID(2))
c.Get(id2)
c.PutBytes(id2, []byte("abc"))
c.Get(id2)
c.Get(id)

data, err := ioutil.ReadFile(filepath.Join(dir, "log.txt"))
if err != nil {
t.Fatal(err)
}
want := `1000000000 miss 0100000000000000000000000000000000000000000000000000000000000000
1000000000 put 0100000000000000000000000000000000000000000000000000000000000000 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3
1000000000 get 0100000000000000000000000000000000000000000000000000000000000000
1000000001 get 0100000000000000000000000000000000000000000000000000000000000000
1000000001 miss 0200000000000000000000000000000000000000000000000000000000000000
1000000001 put 0200000000000000000000000000000000000000000000000000000000000000 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3
1000000001 get 0200000000000000000000000000000000000000000000000000000000000000
1000000001 get 0100000000000000000000000000000000000000000000000000000000000000
`
if string(data) != want {
t.Fatalf("log:\n%s\nwant:\n%s", string(data), want)
}
}

func dummyID(x int) [HashSize]byte {
var out [HashSize]byte
binary.LittleEndian.PutUint64(out[:], uint64(x))
return out
}

func TestCacheTrim(t *testing.T) {
dir, err := ioutil.TempDir("", "cachetest-")
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
@@ -251,25 +201,33 @@ func TestCacheTrim(t *testing.T) {
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2)

// Trim should leave everything alone: it's all too new.
c.Trim()
if err := c.Trim(); err != nil {
// if testenv.SyscallIsNotSupported(err) {
if true {
t.Skipf("skipping: Trim is unsupported (%v)", err)
}
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
data, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)

// Trim less than a day later should not do any work at all.
now = start + 80000
c.Trim()
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data2, err := ioutil.ReadFile(filepath.Join(dir, "trim.txt"))
data2, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
@@ -284,7 +242,9 @@ func TestCacheTrim(t *testing.T) {
// and we haven't looked at it since, so 5 days later it should be gone.
now += 5 * 86400
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
c.Trim()
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
@@ -298,21 +258,27 @@ func TestCacheTrim(t *testing.T) {
// Check that another 5 days later it is still not gone,
// but check by using checkTime, which doesn't bring mtime forward.
now += 5 * 86400
c.Trim()
if err := c.Trim(); err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)

// Half a day later Trim should still be a no-op, because there was a Trim recently.
// Even though the entry for id is now old enough to be trimmed,
// it gets a reprieve until the time comes for a new Trim scan.
now += 86400 / 2
c.Trim()
if err := c.Trim(); err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)

// Another half a day later, Trim should actually run, and it should remove id.
now += 86400/2 + 1
c.Trim()
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(dummyID(1)); err == nil {
t.Fatal("Trim did not remove dummyID(1)")
}
86 changes: 45 additions & 41 deletions cache/default.go
Original file line number Diff line number Diff line change
@@ -6,13 +6,14 @@ package cache

import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
)

// Default returns the default cache to use, or nil if no cache should be used.
// Default returns the default cache to use.
// It never returns nil.
func Default() *Cache {
defaultOnce.Do(initDefaultCache)
return defaultCache
@@ -28,68 +29,71 @@ var (
// README as a courtesy to explain where it came from.
const cacheREADME = `This directory holds cached build artifacts from the Go build system.
Run "go clean -cache" if the directory is getting too large.
Run "go clean -fuzzcache" to delete the fuzz cache.
See golang.org to learn more about Go.
`

// initDefaultCache does the work of finding the default cache
// the first time Default is called.
func initDefaultCache() {
dir, showWarnings := defaultDir()
dir := DefaultDir()
if dir == "off" {
return
}
if err := os.MkdirAll(dir, 0o777); err != nil {
if showWarnings {
fmt.Fprintf(os.Stderr, "go: disabling cache (%s) due to initialization failure: %s\n", dir, err)
if defaultDirErr != nil {
log.Fatalf("build cache is required, but could not be located: %v", defaultDirErr)
}
return
log.Fatalf("build cache is disabled by GOCACHE=off, but required as of Go 1.12")
}
if err := os.MkdirAll(dir, 0777); err != nil {
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
if _, err := os.Stat(filepath.Join(dir, "README")); err != nil {
// Best effort.
ioutil.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0o666)
os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
}

c, err := Open(dir)
if err != nil {
if showWarnings {
fmt.Fprintf(os.Stderr, "go: disabling cache (%s) due to initialization failure: %s\n", dir, err)
}
return
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
defaultCache = c
}

var (
defaultDirOnce sync.Once
defaultDir string
defaultDirErr error
)

// DefaultDir returns the effective GOCACHE setting.
// It returns "off" if the cache is disabled.
func DefaultDir() string {
dir, _ := defaultDir()
return dir
}
// Save the result of the first call to DefaultDir for later use in
// initDefaultCache. cmd/go/main.go explicitly sets GOCACHE so that
// subprocesses will inherit it, but that means initDefaultCache can't
// otherwise distinguish between an explicit "off" and a UserCacheDir error.

// defaultDir returns the effective GOCACHE setting.
// It returns "off" if the cache is disabled.
// The second return value reports whether warnings should
// be shown if the cache fails to initialize.
func defaultDir() (string, bool) {
dir := os.Getenv("GOCACHE")
if dir != "" {
return dir, true
}
defaultDirOnce.Do(func() {
// NOTE: changed from upstream's cfg.Getenv, so it will ignore "go env -w".
// Consider calling "go env" or copying the cfg package instead.
defaultDir = os.Getenv("GOCACHE")
if filepath.IsAbs(defaultDir) || defaultDir == "off" {
return
}
if defaultDir != "" {
defaultDir = "off"
defaultDirErr = fmt.Errorf("GOCACHE is not an absolute path")
return
}

// Compute default location.
dir, err := os.UserCacheDir()
if err != nil {
return "off", true
}
dir = filepath.Join(dir, "go-build")
// Compute default location.
dir, err := os.UserCacheDir()
if err != nil {
defaultDir = "off"
defaultDirErr = fmt.Errorf("GOCACHE is not defined and %v", err)
return
}
defaultDir = filepath.Join(dir, "go-build")
})

// Do this after filepath.Join, so that the path has been cleaned.
showWarnings := true
switch dir {
case "/.cache/go-build":
// probably docker run with -u flag
// https://golang.org/issue/26280
showWarnings = false
}
return dir, showWarnings
return defaultDir
}
68 changes: 0 additions & 68 deletions cache/default_unix_test.go

This file was deleted.

18 changes: 17 additions & 1 deletion cache/hash.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import (
"io"
"os"
"runtime"
"strings"
"sync"
)

@@ -36,7 +37,22 @@ type Hash struct {
// of other versions. This salt will result in additional ActionID files
// in the cache, but not additional copies of the large output files,
// which are still addressed by unsalted SHA256.
var hashSalt = []byte(runtime.Version())
//
// We strip any GOEXPERIMENTs the go tool was built with from this
// version string on the assumption that they shouldn't affect go tool
// execution. This allows bootstrapping to converge faster: dist builds
// go_bootstrap without any experiments, so by stripping experiments
// go_bootstrap and the final go binary will use the same salt.
var hashSalt = []byte(stripExperiment(runtime.Version()))

// stripExperiment strips any GOEXPERIMENT configuration from the Go
// version string.
func stripExperiment(version string) string {
if i := strings.Index(version, " X:"); i >= 0 {
return version[:i]
}
return version
}

// Subkey returns an action ID corresponding to mixing a parent
// action ID with a string description of the subkey.
3 changes: 1 addition & 2 deletions cache/hash_test.go
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ package cache

import (
"fmt"
"io/ioutil"
"os"
"testing"
)
@@ -28,7 +27,7 @@ func TestHash(t *testing.T) {
}

func TestHashFile(t *testing.T) {
f, err := ioutil.TempFile("", "cmd-go-test-")
f, err := os.CreateTemp("", "cmd-go-test-")
if err != nil {
t.Fatal(err)
}
4 changes: 3 additions & 1 deletion cmd/testscript/testdata/noproxy.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# With no .gomodproxy supporting files, we use the GOPROXY from
# the environment.
# Note that Go 1.21 started quoting with single quotes in "go env",
# where older versions used double quotes.
env GOPROXY=0.1.2.3
unquote file.txt
testscript -v file.txt

-- file.txt --
>go env
>[!windows] stdout '^GOPROXY="0.1.2.3"$'
>[!windows] stdout '^GOPROXY=[''"]0.1.2.3[''"]$'
>[windows] stdout '^set GOPROXY=0.1.2.3$'
24 changes: 13 additions & 11 deletions cmd/txtar-addmod/addmod.go
Original file line number Diff line number Diff line change
@@ -21,15 +21,14 @@ import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/rogpeppe/go-internal/module"
"github.com/rogpeppe/go-internal/txtar"
"golang.org/x/mod/module"
"golang.org/x/tools/txtar"
)

func usage() {
@@ -82,7 +81,7 @@ func main1() int {
log.SetFlags(0)

var err error
tmpdir, err = ioutil.TempDir("", "txtar-addmod-")
tmpdir, err = os.MkdirTemp("", "txtar-addmod-")
if err != nil {
log.Fatal(err)
}
@@ -106,7 +105,7 @@ func main1() int {

exitCode := 0
for _, arg := range modules {
if err := ioutil.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0o666); err != nil {
if err := os.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0o666); err != nil {
fatalf("%v", err)
}
run(goCmd, "get", "-d", arg)
@@ -123,20 +122,20 @@ func main1() int {
}
path, vers, dir := f[0], f[1], f[2]

encpath, err := module.EncodePath(path)
encpath, err := module.EscapePath(path)
if err != nil {
log.Printf("failed to encode path %q: %v", path, err)
continue
}
path = encpath

mod, err := ioutil.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".mod"))
mod, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".mod"))
if err != nil {
log.Printf("%s: %v", arg, err)
exitCode = 1
continue
}
info, err := ioutil.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".info"))
info, err := os.ReadFile(filepath.Join(gopath, "pkg/mod/cache/download", path, "@v", vers+".info"))
if err != nil {
log.Printf("%s: %v", arg, err)
exitCode = 1
@@ -149,7 +148,7 @@ func main1() int {
title += "@" + vers
}
dir = filepath.Clean(dir)
modDir := strings.Replace(path, "/", "_", -1) + "_" + vers
modDir := strings.ReplaceAll(path, "/", "_") + "_" + vers
filePrefix := ""
if targetDir == "-" {
filePrefix = ".gomodproxy/" + modDir + "/"
@@ -162,6 +161,9 @@ func main1() int {
{Name: filePrefix + ".info", Data: info},
}
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
@@ -177,7 +179,7 @@ func main1() int {
// not including all files via -all
return nil
}
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return err
}
@@ -201,7 +203,7 @@ func main1() int {
break
}
} else {
if err := ioutil.WriteFile(filepath.Join(targetDir, modDir+".txtar"), data, 0o666); err != nil {
if err := os.WriteFile(filepath.Join(targetDir, modDir+".txtar"), data, 0o666); err != nil {
log.Printf("%s: %v", arg, err)
exitCode = 1
continue
16 changes: 10 additions & 6 deletions cmd/txtar-c/savedir.go
Original file line number Diff line number Diff line change
@@ -8,15 +8,14 @@
//
// txtar-c /path/to/dir >saved.txtar
//
// See https://godoc.org/github.com/rogpeppe/go-internal/txtar for details of the format
// See https://godoc.org/golang.org/x/tools/txtar for details of the format
// and how to parse a txtar file.
package main

import (
"bytes"
stdflag "flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -59,7 +58,10 @@ func main1() int {

a := new(txtar.Archive)
dir = filepath.Clean(dir)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == dir {
return nil
}
@@ -73,9 +75,9 @@ func main1() int {
if !info.Mode().IsRegular() {
return nil
}
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
return err
}
if !utf8.Valid(data) {
log.Printf("%s: ignoring file with invalid UTF-8 data", path)
@@ -103,7 +105,9 @@ func main1() int {
Data: data,
})
return nil
})
}); err != nil {
log.Fatal(err)
}

data := txtar.Format(a)
os.Stdout.Write(data)
6 changes: 3 additions & 3 deletions cmd/txtar-x/extract.go
Original file line number Diff line number Diff line change
@@ -8,14 +8,14 @@
//
// txtar-x [-C root-dir] saved.txt
//
// See https://godoc.org/github.com/rogpeppe/go-internal/txtar for details of the format
// See https://godoc.org/golang.org/x/tools/txtar for details of the format
// and how to parse a txtar file.
package main

import (
"flag"
"fmt"
"io/ioutil"
"io"
"log"
"os"

@@ -47,7 +47,7 @@ func main1() int {

var a *txtar.Archive
if flag.NArg() == 0 {
data, err := ioutil.ReadAll(os.Stdin)
data, err := io.ReadAll(os.Stdin)
if err != nil {
log.Printf("cannot read stdin: %v", err)
return 1
5 changes: 2 additions & 3 deletions cmd/txtar-x/extract_test.go
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ package main

import (
"bytes"
"io/ioutil"
"os"
"testing"

@@ -34,11 +33,11 @@ func unquote(ts *testscript.TestScript, neg bool, args []string) {
}
for _, arg := range args {
file := ts.MkAbs(arg)
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
ts.Check(err)
data = bytes.Replace(data, []byte("\n>"), []byte("\n"), -1)
data = bytes.TrimPrefix(data, []byte(">"))
err = ioutil.WriteFile(file, data, 0o666)
err = os.WriteFile(file, data, 0o666)
ts.Check(err)
}
}
2 changes: 1 addition & 1 deletion diff/diff_test.go
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import (
"path/filepath"
"testing"

"github.com/rogpeppe/go-internal/txtar"
"golang.org/x/tools/txtar"
)

func clean(text []byte) []byte {
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
module github.com/rogpeppe/go-internal

go 1.19
go 1.20

require (
golang.org/x/mod v0.9.0
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/tools v0.1.12
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
41 changes: 21 additions & 20 deletions goproxytest/proxy.go
Original file line number Diff line number Diff line change
@@ -26,18 +26,19 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io/fs"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/rogpeppe/go-internal/module"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/tools/txtar"

"github.com/rogpeppe/go-internal/par"
"github.com/rogpeppe/go-internal/semver"
"github.com/rogpeppe/go-internal/txtar"
)

type Server struct {
@@ -90,32 +91,32 @@ func (srv *Server) Close() {
}

func (srv *Server) readModList() error {
infos, err := ioutil.ReadDir(srv.dir)
entries, err := os.ReadDir(srv.dir)
if err != nil {
return err
}
for _, info := range infos {
name := info.Name()
for _, entry := range entries {
name := entry.Name()
switch {
case strings.HasSuffix(name, ".txt"):
name = strings.TrimSuffix(name, ".txt")
case strings.HasSuffix(name, ".txtar"):
name = strings.TrimSuffix(name, ".txtar")
case info.IsDir():
case entry.IsDir():
default:
continue
}
i := strings.LastIndex(name, "_v")
if i < 0 {
continue
}
encPath := strings.Replace(name[:i], "_", "/", -1)
path, err := module.DecodePath(encPath)
encPath := strings.ReplaceAll(name[:i], "_", "/")
path, err := module.UnescapePath(encPath)
if err != nil {
return fmt.Errorf("cannot decode module path in %q: %v", name, err)
}
encVers := name[i+1:]
vers, err := module.DecodeVersion(encVers)
vers, err := module.UnescapeVersion(encVers)
if err != nil {
return fmt.Errorf("cannot decode module version in %q: %v", name, err)
}
@@ -138,7 +139,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) {
return
}
enc, file := path[:i], path[i+len("/@v/"):]
path, err := module.DecodePath(enc)
path, err := module.UnescapePath(enc)
if err != nil {
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
http.NotFound(w, r)
@@ -166,7 +167,7 @@ func (srv *Server) handler(w http.ResponseWriter, r *http.Request) {
return
}
encVers, ext := file[:i], file[i+1:]
vers, err := module.DecodeVersion(encVers)
vers, err := module.UnescapeVersion(encVers)
if err != nil {
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
http.NotFound(w, r)
@@ -274,18 +275,18 @@ func (srv *Server) findHash(m module.Version) string {
}

func (srv *Server) readArchive(path, vers string) *txtar.Archive {
enc, err := module.EncodePath(path)
enc, err := module.EscapePath(path)
if err != nil {
fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
return nil
}
encVers, err := module.EncodeVersion(vers)
encVers, err := module.EscapeVersion(vers)
if err != nil {
fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
return nil
}

prefix := strings.Replace(enc, "/", "_", -1)
prefix := strings.ReplaceAll(enc, "/", "_")
name := filepath.Join(srv.dir, prefix+"_"+encVers)
txtName := name + ".txt"
txtarName := name + ".txtar"
@@ -299,18 +300,18 @@ func (srv *Server) readArchive(path, vers string) *txtar.Archive {
// fall back to trying a directory
a = new(txtar.Archive)

err = filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
err = filepath.WalkDir(name, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == name && !info.IsDir() {
if path == name && !entry.IsDir() {
return fmt.Errorf("expected a directory root")
}
if info.IsDir() {
if entry.IsDir() {
return nil
}
arpath := filepath.ToSlash(strings.TrimPrefix(path, name+string(os.PathSeparator)))
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return err
}
2 changes: 1 addition & 1 deletion goproxytest/pseudo.go
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import (
"regexp"
"strings"

"github.com/rogpeppe/go-internal/semver"
"golang.org/x/mod/semver"
)

// This code was taken from cmd/go/internal/modfetch/pseudo.go
9 changes: 0 additions & 9 deletions gotooltest/setup.go
Original file line number Diff line number Diff line change
@@ -172,12 +172,3 @@ func (e0 *environ) get(name string) string {
}
return ""
}

func (e *environ) set(name, val string) {
*e = append(*e, name+"="+val)
}

func (e *environ) unset(name string) {
// TODO actually remove the name from the environment.
e.set(name, "")
}
5 changes: 1 addition & 4 deletions gotooltest/testdata/cover.txt
Original file line number Diff line number Diff line change
@@ -13,12 +13,9 @@ stdout 'PASS'
# Then, a 'go test' run with -coverprofile.
# The total coverage after merging profiles should end up being 100%.
# Marking all printlns as covered requires all edge cases to work well.
# Go 1.20 learned to produce and merge multiple coverage profiles,
# so versions before then report a shallow 0% coverage.
go test -vet=off -coverprofile=cover.out -v
stdout 'PASS'
[go1.20] stdout 'coverage: 100\.0%'
[!go1.20] stdout 'coverage: 0\.0%'
stdout 'coverage: 100\.0%'
! stdout 'malformed coverage' # written by "go test" if cover.out is invalid
exists cover.out

68 changes: 68 additions & 0 deletions internal/misspell/misspell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Package misspell impements utilities for basic spelling correction.
package misspell

import (
"unicode/utf8"
)

// AlmostEqual reports whether a and b have Damerau-Levenshtein distance of at
// most 1. That is, it reports whether a can be transformed into b by adding,
// removing or substituting a single rune, or by swapping two adjacent runes.
// Invalid runes are considered equal.
//
// It runs in O(len(a)+len(b)) time.
func AlmostEqual(a, b string) bool {
for len(a) > 0 && len(b) > 0 {
ra, tailA := shiftRune(a)
rb, tailB := shiftRune(b)
if ra == rb {
a, b = tailA, tailB
continue
}
// check for addition/deletion/substitution
if equalValid(a, tailB) || equalValid(tailA, b) || equalValid(tailA, tailB) {
return true
}
if len(tailA) == 0 || len(tailB) == 0 {
return false
}
// check for swap
a, b = tailA, tailB
Ra, tailA := shiftRune(tailA)
Rb, tailB := shiftRune(tailB)
return ra == Rb && Ra == rb && equalValid(tailA, tailB)
}
if len(a) == 0 {
return len(b) == 0 || singleRune(b)
}
return singleRune(a)
}

// singleRune reports whether s consists of a single UTF-8 codepoint.
func singleRune(s string) bool {
_, n := utf8.DecodeRuneInString(s)
return n == len(s)
}

// shiftRune splits off the first UTF-8 codepoint from s and returns it and the
// rest of the string. It panics if s is empty.
func shiftRune(s string) (rune, string) {
if len(s) == 0 {
panic(s)
}
r, n := utf8.DecodeRuneInString(s)
return r, s[n:]
}

// equalValid reports whether a and b are equal, if invalid code points are considered equal.
func equalValid(a, b string) bool {
var ra, rb rune
for len(a) > 0 && len(b) > 0 {
ra, a = shiftRune(a)
rb, b = shiftRune(b)
if ra != rb {
return false
}
}
return len(a) == 0 && len(b) == 0
}
100 changes: 100 additions & 0 deletions internal/misspell/misspell_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package misspell

import (
"math"
"testing"
)

func TestAlmostEqual(t *testing.T) {
t.Parallel()

tcs := []struct {
inA string
inB string
want bool
}{
{"", "", true},
{"", "a", true},
{"a", "a", true},
{"a", "b", true},
{"hello", "hell", true},
{"hello", "jello", true},
{"hello", "helol", true},
{"hello", "jelol", false},
}
for _, tc := range tcs {
got := AlmostEqual(tc.inA, tc.inB)
if got != tc.want {
t.Errorf("AlmostEqual(%q, %q) = %v, want %v", tc.inA, tc.inB, got, tc.want)
}
// two tests for the price of one \o/
if got != AlmostEqual(tc.inB, tc.inA) {
t.Errorf("AlmostEqual(%q, %q) == %v != AlmostEqual(%q, %q)", tc.inA, tc.inB, got, tc.inB, tc.inA)
}
}
}

func FuzzAlmostEqual(f *testing.F) {
f.Add("", "")
f.Add("", "a")
f.Add("a", "a")
f.Add("a", "b")
f.Add("hello", "hell")
f.Add("hello", "jello")
f.Add("hello", "helol")
f.Add("hello", "jelol")
f.Fuzz(func(t *testing.T, a, b string) {
if len(a) > 10 || len(b) > 10 {
// longer strings won't add coverage, but take longer to check
return
}
d := editDistance([]rune(a), []rune(b))
got := AlmostEqual(a, b)
if want := d <= 1; got != want {
t.Errorf("AlmostEqual(%q, %q) = %v, editDistance(%q, %q) = %d", a, b, got, a, b, d)
}
if got != AlmostEqual(b, a) {
t.Errorf("AlmostEqual(%q, %q) == %v != AlmostEqual(%q, %q)", a, b, got, b, a)
}
})
}

// editDistance returns the Damerau-Levenshtein distance between a and b. It is
// inefficient, but by keeping almost verbatim to the recursive definition from
// Wikipedia, hopefully "obviously correct" and thus suitable for the fuzzing
// test of AlmostEqual.
func editDistance(a, b []rune) int {
i, j := len(a), len(b)
m := math.MaxInt
if i == 0 && j == 0 {
return 0
}
if i > 0 {
m = min(m, editDistance(a[1:], b)+1)
}
if j > 0 {
m = min(m, editDistance(a, b[1:])+1)
}
if i > 0 && j > 0 {
d := editDistance(a[1:], b[1:])
if a[0] != b[0] {
d += 1
}
m = min(m, d)
}
if i > 1 && j > 1 && a[0] == b[1] && a[1] == b[0] {
d := editDistance(a[2:], b[2:])
if a[0] != b[0] {
d += 1
}
m = min(m, d)
}
return m
}

func min(a, b int) int {
if a < b {
return a
}
return b
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go test fuzz v1
string("")
string("00")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go test fuzz v1
string("\x980")
string("0\xb70")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go test fuzz v1
string("OOOOOOOO000")
string("0000000000000")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go test fuzz v1
string("OOOOOOOO000")
string("0000000000\x1000")
1 change: 0 additions & 1 deletion internal/os/execpath/lp_js.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build js && wasm
// +build js,wasm

package execpath

1 change: 0 additions & 1 deletion internal/os/execpath/lp_unix.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris

package execpath

173 changes: 0 additions & 173 deletions internal/syscall/windows/registry/key.go

This file was deleted.

7 changes: 0 additions & 7 deletions internal/syscall/windows/registry/mksyscall.go

This file was deleted.

32 changes: 0 additions & 32 deletions internal/syscall/windows/registry/syscall.go

This file was deleted.

385 changes: 0 additions & 385 deletions internal/syscall/windows/registry/value.go

This file was deleted.

111 changes: 0 additions & 111 deletions internal/syscall/windows/registry/zsyscall_windows.go

This file was deleted.

78 changes: 0 additions & 78 deletions internal/textutil/diff.go

This file was deleted.

45 changes: 0 additions & 45 deletions internal/textutil/diff_test.go

This file was deleted.

9 changes: 0 additions & 9 deletions internal/textutil/doc.go

This file was deleted.

1 change: 0 additions & 1 deletion lockedfile/internal/filelock/filelock_fcntl.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build aix || (solaris && !illumos)
// +build aix solaris,!illumos

// This code implements the filelock API using POSIX 'fcntl' locks, which attach
// to an (inode, process) pair rather than a file descriptor. To avoid unlocking
3 changes: 1 addition & 2 deletions lockedfile/internal/filelock/filelock_other.go
Original file line number Diff line number Diff line change
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris && !windows
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris,!windows
//go:build !unix && !windows

package filelock

37 changes: 0 additions & 37 deletions lockedfile/internal/filelock/filelock_plan9.go

This file was deleted.

1 change: 0 additions & 1 deletion lockedfile/internal/filelock/filelock_test.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build !js && !plan9
// +build !js,!plan9

package filelock_test

1 change: 0 additions & 1 deletion lockedfile/internal/filelock/filelock_unix.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
// +build darwin dragonfly freebsd illumos linux netbsd openbsd

package filelock

1 change: 0 additions & 1 deletion lockedfile/internal/filelock/filelock_windows.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.

//go:build windows
// +build windows

package filelock

33 changes: 24 additions & 9 deletions lockedfile/lockedfile_test.go
Original file line number Diff line number Diff line change
@@ -43,19 +43,32 @@ func mustBlock(t *testing.T, desc string, f func()) (wait func(*testing.T)) {
close(done)
}()

timer := time.NewTimer(quiescent)
defer timer.Stop()
select {
case <-done:
t.Fatalf("%s unexpectedly did not block", desc)
return nil
case <-timer.C:
}

return func(t *testing.T) {
logTimer := time.NewTimer(quiescent)
defer logTimer.Stop()

case <-time.After(quiescent):
return func(t *testing.T) {
select {
case <-logTimer.C:
// We expect the operation to have unblocked by now,
// but maybe it's just slow. Write to the test log
// in case the test times out, but don't fail it.
t.Helper()
select {
case <-time.After(probablyStillBlocked):
t.Fatalf("%s is unexpectedly still blocked after %v", desc, probablyStillBlocked)
case <-done:
}
t.Logf("%s is unexpectedly still blocked after %v", desc, quiescent)

// Wait for the operation to actually complete, no matter how long it
// takes. If the test has deadlocked, this will cause the test to time out
// and dump goroutines.
<-done

case <-done:
}
}
}
@@ -242,10 +255,12 @@ locked:
if _, err := os.Stat(filepath.Join(dir, "locked")); !os.IsNotExist(err) {
break locked
}
timer := time.NewTimer(1 * time.Millisecond)
select {
case <-qDone:
timer.Stop()
break locked
case <-time.After(1 * time.Millisecond):
case <-timer.C:
}
}

59 changes: 59 additions & 0 deletions modfile/forward.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Package modfile implements parsing and formatting for go.mod files.
//
// This is now just a simple forwarding layer over golang.org/x/mod/modfile
// apart from the ParseGopkgIn function which doesn't exist there.
//
// See that package for documentation.
//
// Deprecated: use [golang.org/x/mod/modfile] instead.
package modfile

import (
"golang.org/x/mod/modfile"
)

type Position = modfile.Position
type Expr = modfile.Expr
type Comment = modfile.Comment
type Comments = modfile.Comments
type FileSyntax = modfile.FileSyntax
type CommentBlock = modfile.CommentBlock
type Line = modfile.Line
type LineBlock = modfile.LineBlock
type LParen = modfile.LParen
type RParen = modfile.RParen
type File = modfile.File
type Module = modfile.Module
type Go = modfile.Go
type Require = modfile.Require
type Exclude = modfile.Exclude
type Replace = modfile.Replace
type VersionFixer = modfile.VersionFixer

func Format(f *FileSyntax) []byte {
return modfile.Format(f)
}

func ModulePath(mod []byte) string {
return modfile.ModulePath(mod)
}

func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
return modfile.Parse(file, data, fix)
}

func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
return modfile.ParseLax(file, data, fix)
}

func IsDirectoryPath(ns string) bool {
return modfile.IsDirectoryPath(ns)
}

func MustQuote(s string) bool {
return modfile.MustQuote(s)
}

func AutoQuote(s string) string {
return modfile.AutoQuote(s)
}
164 changes: 0 additions & 164 deletions modfile/print.go

This file was deleted.

868 changes: 0 additions & 868 deletions modfile/read.go

This file was deleted.

334 changes: 0 additions & 334 deletions modfile/read_test.go

This file was deleted.

Loading