diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5649f292..be458cf9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,52 +11,31 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.17.x, 1.18.x] - os: [ubuntu-latest, macos-latest, windows-latest] + go-version: + - '1.20.x' + - '1.21.x' + os: + - ubuntu-latest + - macos-latest + - windows-latest runs-on: ${{ matrix.os }} steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v3 + cache: false # our tests are quick enough - name: Test run: | go test ./... go test -race ./... - name: Tidy - if: matrix.os == 'ubuntu-latest' # 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 test -z "$(gofmt -d .)" || (gofmt -d . && false) test -z "$(git status --porcelain)" || (git status; git diff && false) - - test-gotip: - runs-on: ubuntu-latest - continue-on-error: true # master breaks sometimes - steps: - - name: Install Go - env: - GO_COMMIT: 2cfbef438049fd4c3f73d1562773ad1f93900897 # 2022-06-09 - run: | - cd $HOME - mkdir $HOME/gotip - cd $HOME/gotip - - wget -O gotip.tar.gz https://go.googlesource.com/go/+archive/${GO_COMMIT}.tar.gz - tar -xf gotip.tar.gz - echo "devel go1.19-${GO_COMMIT}" >VERSION - - cd src - ./make.bash - echo "GOROOT=$HOME/gotip" >>$GITHUB_ENV - echo "$HOME/gotip/bin" >>$GITHUB_PATH - - name: Checkout code - uses: actions/checkout@v3 - - name: Test - run: | - go version - go test ./... diff --git a/README.md b/README.md index e21b28d1..bd1b79cb 100644 --- a/README.md +++ b/README.md @@ -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. @@ -14,3 +23,23 @@ Included are the following: - testenv: information on the current testing environment. - testscript: script-based testing based on txtar files - txtar: simple text-based file archives for testing. + +### testscript + +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. diff --git a/cache/cache.go b/cache/cache.go index e6fe3d30..93c90535 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -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 \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") +} diff --git a/cache/cache_test.go b/cache/cache_test.go index e5d1adbc..5ff84c2b 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -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,55 +143,6 @@ 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)) @@ -200,7 +150,7 @@ func dummyID(x int) [HashSize]byte { } func TestCacheTrim(t *testing.T) { - dir, err := ioutil.TempDir("", "cachetest-") + dir, err := os.MkdirTemp("", "cachetest-") if err != nil { t.Fatal(err) } @@ -251,12 +201,18 @@ 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) } @@ -264,12 +220,14 @@ func TestCacheTrim(t *testing.T) { // 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,7 +258,9 @@ 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) @@ -306,13 +268,17 @@ func TestCacheTrim(t *testing.T) { // 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)") } diff --git a/cache/default.go b/cache/default.go index d641931e..a20b33cd 100644 --- a/cache/default.go +++ b/cache/default.go @@ -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 } diff --git a/cache/default_unix_test.go b/cache/default_unix_test.go deleted file mode 100644 index ddc95a5e..00000000 --- a/cache/default_unix_test.go +++ /dev/null @@ -1,68 +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. - -//go:build !windows && !darwin && !plan9 -// +build !windows,!darwin,!plan9 - -package cache - -import ( - "os" - "strings" - "testing" -) - -func TestDefaultDir(t *testing.T) { - goCacheDir := "/tmp/test-go-cache" - xdgCacheDir := "/tmp/test-xdg-cache" - homeDir := "/tmp/test-home" - - // undo env changes when finished - defer func(GOCACHE, XDG_CACHE_HOME, HOME string) { - os.Setenv("GOCACHE", GOCACHE) - os.Setenv("XDG_CACHE_HOME", XDG_CACHE_HOME) - os.Setenv("HOME", HOME) - }(os.Getenv("GOCACHE"), os.Getenv("XDG_CACHE_HOME"), os.Getenv("HOME")) - - os.Setenv("GOCACHE", goCacheDir) - os.Setenv("XDG_CACHE_HOME", xdgCacheDir) - os.Setenv("HOME", homeDir) - - dir, showWarnings := defaultDir() - if dir != goCacheDir { - t.Errorf("Cache DefaultDir %q should be $GOCACHE %q", dir, goCacheDir) - } - if !showWarnings { - t.Error("Warnings should be shown when $GOCACHE is set") - } - - os.Unsetenv("GOCACHE") - dir, showWarnings = defaultDir() - if !strings.HasPrefix(dir, xdgCacheDir+"/") { - t.Errorf("Cache DefaultDir %q should be under $XDG_CACHE_HOME %q when $GOCACHE is unset", dir, xdgCacheDir) - } - if !showWarnings { - t.Error("Warnings should be shown when $XDG_CACHE_HOME is set") - } - - os.Unsetenv("XDG_CACHE_HOME") - dir, showWarnings = defaultDir() - if !strings.HasPrefix(dir, homeDir+"/.cache/") { - t.Errorf("Cache DefaultDir %q should be under $HOME/.cache %q when $GOCACHE and $XDG_CACHE_HOME are unset", dir, homeDir+"/.cache") - } - if !showWarnings { - t.Error("Warnings should be shown when $HOME is not /") - } - - os.Unsetenv("HOME") - if dir, _ := defaultDir(); dir != "off" { - t.Error("Cache not disabled when $GOCACHE, $XDG_CACHE_HOME, and $HOME are unset") - } - - os.Setenv("HOME", "/") - if _, showWarnings := defaultDir(); showWarnings { - // https://golang.org/issue/26280 - t.Error("Cache initialization warnings should be squelched when $GOCACHE and $XDG_CACHE_HOME are unset and $HOME is /") - } -} diff --git a/cache/hash.go b/cache/hash.go index e4bb2a34..4f79c315 100644 --- a/cache/hash.go +++ b/cache/hash.go @@ -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. diff --git a/cache/hash_test.go b/cache/hash_test.go index 3bf71430..a0356771 100644 --- a/cache/hash_test.go +++ b/cache/hash_test.go @@ -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) } diff --git a/cmd/testscript/help.go b/cmd/testscript/help.go index d4808b02..0b3975cd 100644 --- a/cmd/testscript/help.go +++ b/cmd/testscript/help.go @@ -14,7 +14,7 @@ The testscript command runs github.com/rogpeppe/go-internal/testscript scripts in a fresh temporary work directory tree. Usage: - testscript [-v] [-e VAR[=value]]... [-u] [-work] files... + testscript [-v] [-e VAR[=value]]... [-u] [-continue] [-work] files... The testscript command is designed to make it easy to create self-contained reproductions of command sequences. @@ -42,6 +42,9 @@ succeed and the testscript file will be updated to reflect the actual content. As such, this is the cmd/testcript equivalent of testscript.Params.UpdateScripts. +The -continue flag specifies that if an error occurs, the script will continue running. +All errors will be printed and the exit status will be false. + The -work flag prints the temporary work directory path before running each script, and does not remove that directory when testscript exits. diff --git a/cmd/testscript/main.go b/cmd/testscript/main.go index d3b1d25f..6b2629d6 100644 --- a/cmd/testscript/main.go +++ b/cmd/testscript/main.go @@ -65,6 +65,7 @@ func mainerr() (retErr error) { var envVars envVarsFlag fUpdate := fs.Bool("u", false, "update archive file if a cmp fails") fWork := fs.Bool("work", false, "print temporary work directory and do not remove when done") + fContinue := fs.Bool("continue", false, "continue running the script if an error occurs") fVerbose := fs.Bool("v", false, "run tests verbosely") fs.Var(&envVars, "e", "pass through environment variable to script (can appear multiple times)") if err := fs.Parse(os.Args[1:]); err != nil { @@ -101,10 +102,11 @@ func mainerr() (retErr error) { } tr := testRunner{ - update: *fUpdate, - verbose: *fVerbose, - env: envVars.vals, - testWork: *fWork, + update: *fUpdate, + continueOnError: *fContinue, + verbose: *fVerbose, + env: envVars.vals, + testWork: *fWork, } dirNames := make(map[string]int) @@ -139,6 +141,10 @@ type testRunner struct { // updated in the case of any cmp failures. update bool + // continueOnError indicates that T.FailNow should not panic, allowing the + // test script to continue running. Note that T is still marked as failed. + continueOnError bool + // verbose indicates the running of the script should be noisy. verbose bool @@ -203,8 +209,9 @@ func (tr *testRunner) run(runDir, filename string) error { } p := testscript.Params{ - Dir: runDir, - UpdateScripts: tr.update, + Dir: runDir, + UpdateScripts: tr.update, + ContinueOnError: tr.continueOnError, } if _, err := exec.LookPath("go"); err == nil { @@ -271,7 +278,7 @@ func (tr *testRunner) run(runDir, filename string) error { }) } - r := runT{ + r := &runT{ verbose: tr.verbose, } @@ -286,6 +293,12 @@ func (tr *testRunner) run(runDir, filename string) error { } }() testscript.RunT(r, p) + + // When continueOnError is true, FailNow does not call panic(failedRun). + // We still want err to be set, as the script resulted in a failure. + if r.Failed() { + err = failedRun + } }() if err != nil { @@ -338,40 +351,40 @@ type runT struct { failed int32 } -func (r runT) Skip(is ...interface{}) { +func (r *runT) Skip(is ...interface{}) { panic(skipRun) } -func (r runT) Fatal(is ...interface{}) { +func (r *runT) Fatal(is ...interface{}) { r.Log(is...) r.FailNow() } -func (r runT) Parallel() { +func (r *runT) Parallel() { // No-op for now; we are currently only running a single script in a // testscript instance. } -func (r runT) Log(is ...interface{}) { +func (r *runT) Log(is ...interface{}) { fmt.Print(is...) } -func (r runT) FailNow() { +func (r *runT) FailNow() { atomic.StoreInt32(&r.failed, 1) panic(failedRun) } -func (r runT) Failed() bool { +func (r *runT) Failed() bool { return atomic.LoadInt32(&r.failed) != 0 } -func (r runT) Run(n string, f func(t testscript.T)) { +func (r *runT) Run(n string, f func(t testscript.T)) { // For now we we don't top/tail the run of a subtest. We are currently only // running a single script in a testscript instance, which means that we // will only have a single subtest. f(r) } -func (r runT) Verbose() bool { +func (r *runT) Verbose() bool { return r.verbose } diff --git a/cmd/testscript/testdata/continue.txt b/cmd/testscript/testdata/continue.txt new file mode 100644 index 00000000..78f62114 --- /dev/null +++ b/cmd/testscript/testdata/continue.txt @@ -0,0 +1,16 @@ +# should support -continue +unquote file.txt + +# Running with continue, the testscript command itself +# should fail, but we should see the results of executing +# both commands. +! testscript -continue file.txt +stdout 'grep banana in' +stdout 'no match for `banana` found in in' +stdout 'grep apple in' + +-- file.txt -- +>grep banana in +>grep apple in +>-- in -- +>apple diff --git a/cmd/testscript/testdata/env_var_with_go.txt b/cmd/testscript/testdata/env_var_with_go.txt index cf445cac..aa23111e 100644 --- a/cmd/testscript/testdata/env_var_with_go.txt +++ b/cmd/testscript/testdata/env_var_with_go.txt @@ -13,8 +13,7 @@ unquote withproxy.txt testscript -v noproxy.txt stdout ^BANANA=$ stdout '^GOPATH=\$WORK[/\\]\.gopath'$ -[!go1.13] stdout ^GOPROXY=$ -[go1.13] stdout ^GOPROXY=https://proxy.golang.org,direct$ +stdout ^GOPROXY=https://proxy.golang.org,direct$ ! stderr .+ env BANANA=banana @@ -26,8 +25,7 @@ env GOPROXY= testscript -v noproxy.txt stdout ^BANANA=$ stdout '^GOPATH=\$WORK[/\\]\.gopath'$ -[!go1.13] stdout ^GOPROXY=$ -[go1.13] stdout ^GOPROXY=https://proxy.golang.org,direct$ +stdout ^GOPROXY=https://proxy.golang.org,direct$ ! stderr .+ # no GOPROXY, no pass-through, with proxy diff --git a/cmd/testscript/testdata/noproxy.txt b/cmd/testscript/testdata/noproxy.txt index f725a278..2de54859 100644 --- a/cmd/testscript/testdata/noproxy.txt +++ b/cmd/testscript/testdata/noproxy.txt @@ -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$' diff --git a/cmd/txtar-addmod/addmod.go b/cmd/txtar-addmod/addmod.go index cb63de97..e58514a6 100644 --- a/cmd/txtar-addmod/addmod.go +++ b/cmd/txtar-addmod/addmod.go @@ -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 diff --git a/cmd/txtar-c/savedir.go b/cmd/txtar-c/savedir.go index 07f0ab27..bc2f692b 100644 --- a/cmd/txtar-c/savedir.go +++ b/cmd/txtar-c/savedir.go @@ -8,16 +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" @@ -60,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 } @@ -74,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) @@ -104,7 +105,9 @@ func main1() int { Data: data, }) return nil - }) + }); err != nil { + log.Fatal(err) + } data := txtar.Format(a) os.Stdout.Write(data) diff --git a/cmd/txtar-goproxy/main.go b/cmd/txtar-goproxy/main.go index 161cc769..c4f0553a 100644 --- a/cmd/txtar-goproxy/main.go +++ b/cmd/txtar-goproxy/main.go @@ -8,9 +8,9 @@ // This allows interactive experimentation with the set of proxied modules. // For example: // -// cd cmd/go -// go test -proxy=localhost:1234 & -// export GOPROXY=http://localhost:1234/mod +// cd cmd/go +// go test -proxy=localhost:1234 & +// export GOPROXY=http://localhost:1234/mod // // and then run go commands as usual. package main diff --git a/cmd/txtar-x/extract.go b/cmd/txtar-x/extract.go index 38d1a9fd..713026e7 100644 --- a/cmd/txtar-x/extract.go +++ b/cmd/txtar-x/extract.go @@ -8,15 +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" @@ -48,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 diff --git a/cmd/txtar-x/extract_test.go b/cmd/txtar-x/extract_test.go index 3d07e30f..97e8e7e3 100644 --- a/cmd/txtar-x/extract_test.go +++ b/cmd/txtar-x/extract_test.go @@ -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) } } diff --git a/diff/diff.go b/diff/diff.go new file mode 100644 index 00000000..47b28567 --- /dev/null +++ b/diff/diff.go @@ -0,0 +1,261 @@ +// Copyright 2022 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 diff + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +// A pair is a pair of values tracked for both the x and y side of a diff. +// It is typically a pair of line indexes. +type pair struct{ x, y int } + +// Diff returns an anchored diff of the two texts old and new +// in the “unified diff” format. If old and new are identical, +// Diff returns a nil slice (no output). +// +// Unix diff implementations typically look for a diff with +// the smallest number of lines inserted and removed, +// which can in the worst case take time quadratic in the +// number of lines in the texts. As a result, many implementations +// either can be made to run for a long time or cut off the search +// after a predetermined amount of work. +// +// In contrast, this implementation looks for a diff with the +// smallest number of “unique” lines inserted and removed, +// where unique means a line that appears just once in both old and new. +// We call this an “anchored diff” because the unique lines anchor +// the chosen matching regions. An anchored diff is usually clearer +// than a standard diff, because the algorithm does not try to +// reuse unrelated blank lines or closing braces. +// The algorithm also guarantees to run in O(n log n) time +// instead of the standard O(n²) time. +// +// Some systems call this approach a “patience diff,” named for +// the “patience sorting” algorithm, itself named for a solitaire card game. +// We avoid that name for two reasons. First, the name has been used +// for a few different variants of the algorithm, so it is imprecise. +// Second, the name is frequently interpreted as meaning that you have +// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, +// when in fact the algorithm is faster than the standard one. +func Diff(oldName string, old []byte, newName string, new []byte) []byte { + if bytes.Equal(old, new) { + return nil + } + x := lines(old) + y := lines(new) + + // Print diff header. + var out bytes.Buffer + fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) + fmt.Fprintf(&out, "--- %s\n", oldName) + fmt.Fprintf(&out, "+++ %s\n", newName) + + // Loop over matches to consider, + // expanding each match to include surrounding lines, + // and then printing diff chunks. + // To avoid setup/teardown cases outside the loop, + // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair + // in the sequence of matches. + var ( + done pair // printed up to x[:done.x] and y[:done.y] + chunk pair // start lines of current chunk + count pair // number of lines from each side in current chunk + ctext []string // lines for current chunk + ) + for _, m := range tgs(x, y) { + if m.x < done.x { + // Already handled scanning forward from earlier match. + continue + } + + // Expand matching lines as far possible, + // establishing that x[start.x:end.x] == y[start.y:end.y]. + // Note that on the first (or last) iteration we may (or definitey do) + // have an empty match: start.x==end.x and start.y==end.y. + start := m + for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { + start.x-- + start.y-- + } + end := m + for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { + end.x++ + end.y++ + } + + // Emit the mismatched lines before start into this chunk. + // (No effect on first sentinel iteration, when start = {0,0}.) + for _, s := range x[done.x:start.x] { + ctext = append(ctext, "-"+s) + count.x++ + } + for _, s := range y[done.y:start.y] { + ctext = append(ctext, "+"+s) + count.y++ + } + + // If we're not at EOF and have too few common lines, + // the chunk includes all the common lines and continues. + const C = 3 // number of context lines + if (end.x < len(x) || end.y < len(y)) && + (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { + for _, s := range x[start.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + continue + } + + // End chunk with common lines for context. + if len(ctext) > 0 { + n := end.x - start.x + if n > C { + n = C + } + for _, s := range x[start.x : start.x+n] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = pair{start.x + n, start.y + n} + + // Format and emit chunk. + // Convert line numbers to 1-indexed. + // Special case: empty file shows up as 0,0 not 1,0. + if count.x > 0 { + chunk.x++ + } + if count.y > 0 { + chunk.y++ + } + fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) + for _, s := range ctext { + out.WriteString(s) + } + count.x = 0 + count.y = 0 + ctext = ctext[:0] + } + + // If we reached EOF, we're done. + if end.x >= len(x) && end.y >= len(y) { + break + } + + // Otherwise start a new chunk. + chunk = pair{end.x - C, end.y - C} + for _, s := range x[chunk.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + } + + return out.Bytes() +} + +// lines returns the lines in the file x, including newlines. +// If the file does not end in a newline, one is supplied +// along with a warning about the missing newline. +func lines(x []byte) []string { + l := strings.SplitAfter(string(x), "\n") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } else { + // Treat last line as having a message about the missing newline attached, + // using the same text as BSD/GNU diff (including the leading backslash). + l[len(l)-1] += "\n\\ No newline at end of file\n" + } + return l +} + +// tgs returns the pairs of indexes of the longest common subsequence +// of unique lines in x and y, where a unique line is one that appears +// once in x and once in y. +// +// The longest common subsequence algorithm is as described in +// Thomas G. Szymanski, “A Special Case of the Maximal Common +// Subsequence Problem,” Princeton TR #170 (January 1975), +// available at https://research.swtch.com/tgs170.pdf. +func tgs(x, y []string) []pair { + // Count the number of times each string appears in a and b. + // We only care about 0, 1, many, counted as 0, -1, -2 + // for the x side and 0, -4, -8 for the y side. + // Using negative numbers now lets us distinguish positive line numbers later. + m := make(map[string]int) + for _, s := range x { + if c := m[s]; c > -2 { + m[s] = c - 1 + } + } + for _, s := range y { + if c := m[s]; c > -8 { + m[s] = c - 4 + } + } + + // Now unique strings can be identified by m[s] = -1+-4. + // + // Gather the indexes of those strings in x and y, building: + // xi[i] = increasing indexes of unique strings in x. + // yi[i] = increasing indexes of unique strings in y. + // inv[i] = index j such that x[xi[i]] = y[yi[j]]. + var xi, yi, inv []int + for i, s := range y { + if m[s] == -1+-4 { + m[s] = len(yi) + yi = append(yi, i) + } + } + for i, s := range x { + if j, ok := m[s]; ok && j >= 0 { + xi = append(xi, i) + inv = append(inv, j) + } + } + + // Apply Algorithm A from Szymanski's paper. + // In those terms, A = J = inv and B = [0, n). + // We add sentinel pairs {0,0}, and {len(x),len(y)} + // to the returned sequence, to help the processing loop. + J := inv + n := len(xi) + T := make([]int, n) + L := make([]int, n) + for i := range T { + T[i] = n + 1 + } + for i := 0; i < n; i++ { + k := sort.Search(n, func(k int) bool { + return T[k] >= J[i] + }) + T[k] = J[i] + L[i] = k + 1 + } + k := 0 + for _, v := range L { + if k < v { + k = v + } + } + seq := make([]pair, 2+k) + seq[1+k] = pair{len(x), len(y)} // sentinel at end + lastj := n + for i := n - 1; i >= 0; i-- { + if L[i] == k && J[i] < lastj { + seq[k] = pair{xi[i], yi[J[i]]} + k-- + } + } + seq[0] = pair{0, 0} // sentinel at start + return seq +} diff --git a/diff/diff_test.go b/diff/diff_test.go new file mode 100644 index 00000000..ee15b008 --- /dev/null +++ b/diff/diff_test.go @@ -0,0 +1,44 @@ +// Copyright 2022 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 diff + +import ( + "bytes" + "path/filepath" + "testing" + + "golang.org/x/tools/txtar" +) + +func clean(text []byte) []byte { + text = bytes.ReplaceAll(text, []byte("$\n"), []byte("\n")) + text = bytes.TrimSuffix(text, []byte("^D\n")) + return text +} + +func Test(t *testing.T) { + files, _ := filepath.Glob("testdata/*.txt") + if len(files) == 0 { + t.Fatalf("no testdata") + } + + for _, file := range files { + t.Run(filepath.Base(file), func(t *testing.T) { + a, err := txtar.ParseFile(file) + if err != nil { + t.Fatal(err) + } + if len(a.Files) != 3 || a.Files[2].Name != "diff" { + t.Fatalf("%s: want three files, third named \"diff\"", file) + } + diffs := Diff(a.Files[0].Name, clean(a.Files[0].Data), a.Files[1].Name, clean(a.Files[1].Data)) + want := clean(a.Files[2].Data) + if !bytes.Equal(diffs, want) { + t.Fatalf("%s: have:\n%s\nwant:\n%s\n%s", file, + diffs, want, Diff("have", diffs, "want", want)) + } + }) + } +} diff --git a/diff/testdata/allnew.txt b/diff/testdata/allnew.txt new file mode 100644 index 00000000..88756492 --- /dev/null +++ b/diff/testdata/allnew.txt @@ -0,0 +1,13 @@ +-- old -- +-- new -- +a +b +c +-- diff -- +diff old new +--- old ++++ new +@@ -0,0 +1,3 @@ ++a ++b ++c diff --git a/diff/testdata/allold.txt b/diff/testdata/allold.txt new file mode 100644 index 00000000..bcc9ac0e --- /dev/null +++ b/diff/testdata/allold.txt @@ -0,0 +1,13 @@ +-- old -- +a +b +c +-- new -- +-- diff -- +diff old new +--- old ++++ new +@@ -1,3 +0,0 @@ +-a +-b +-c diff --git a/diff/testdata/basic.txt b/diff/testdata/basic.txt new file mode 100644 index 00000000..d2565b5d --- /dev/null +++ b/diff/testdata/basic.txt @@ -0,0 +1,35 @@ +Example from Hunt and McIlroy, “An Algorithm for Differential File Comparison.” +https://www.cs.dartmouth.edu/~doug/diff.pdf + +-- old -- +a +b +c +d +e +f +g +-- new -- +w +a +b +x +y +z +e +-- diff -- +diff old new +--- old ++++ new +@@ -1,7 +1,7 @@ ++w + a + b +-c +-d ++x ++y ++z + e +-f +-g diff --git a/diff/testdata/dups.txt b/diff/testdata/dups.txt new file mode 100644 index 00000000..d10524d0 --- /dev/null +++ b/diff/testdata/dups.txt @@ -0,0 +1,40 @@ +-- old -- +a + +b + +c + +d + +e + +f +-- new -- +a + +B + +C + +d + +e + +f +-- diff -- +diff old new +--- old ++++ new +@@ -1,8 +1,8 @@ + a + $ +-b +- +-c ++B ++ ++C + $ + d + $ diff --git a/diff/testdata/end.txt b/diff/testdata/end.txt new file mode 100644 index 00000000..158637c1 --- /dev/null +++ b/diff/testdata/end.txt @@ -0,0 +1,38 @@ +-- old -- +1 +2 +3 +4 +5 +6 +7 +eight +nine +ten +eleven +-- new -- +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +-- diff -- +diff old new +--- old ++++ new +@@ -5,7 +5,6 @@ + 5 + 6 + 7 +-eight +-nine +-ten +-eleven ++8 ++9 ++10 diff --git a/diff/testdata/eof.txt b/diff/testdata/eof.txt new file mode 100644 index 00000000..5dc145c4 --- /dev/null +++ b/diff/testdata/eof.txt @@ -0,0 +1,9 @@ +-- old -- +a +b +c^D +-- new -- +a +b +c^D +-- diff -- diff --git a/diff/testdata/eof1.txt b/diff/testdata/eof1.txt new file mode 100644 index 00000000..1ebf621e --- /dev/null +++ b/diff/testdata/eof1.txt @@ -0,0 +1,18 @@ +-- old -- +a +b +c +-- new -- +a +b +c^D +-- diff -- +diff old new +--- old ++++ new +@@ -1,3 +1,3 @@ + a + b +-c ++c +\ No newline at end of file diff --git a/diff/testdata/eof2.txt b/diff/testdata/eof2.txt new file mode 100644 index 00000000..047705e6 --- /dev/null +++ b/diff/testdata/eof2.txt @@ -0,0 +1,18 @@ +-- old -- +a +b +c^D +-- new -- +a +b +c +-- diff -- +diff old new +--- old ++++ new +@@ -1,3 +1,3 @@ + a + b +-c +\ No newline at end of file ++c diff --git a/diff/testdata/long.txt b/diff/testdata/long.txt new file mode 100644 index 00000000..3fc99f71 --- /dev/null +++ b/diff/testdata/long.txt @@ -0,0 +1,62 @@ +-- old -- +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +14½ +15 +16 +17 +18 +19 +20 +-- new -- +1 +2 +3 +4 +5 +6 +8 +9 +10 +11 +12 +13 +14 +17 +18 +19 +20 +-- diff -- +diff old new +--- old ++++ new +@@ -4,7 +4,6 @@ + 4 + 5 + 6 +-7 + 8 + 9 + 10 +@@ -12,9 +11,6 @@ + 12 + 13 + 14 +-14½ +-15 +-16 + 17 + 18 + 19 diff --git a/diff/testdata/same.txt b/diff/testdata/same.txt new file mode 100644 index 00000000..86b1100d --- /dev/null +++ b/diff/testdata/same.txt @@ -0,0 +1,5 @@ +-- old -- +hello world +-- new -- +hello world +-- diff -- diff --git a/diff/testdata/start.txt b/diff/testdata/start.txt new file mode 100644 index 00000000..217b2fdc --- /dev/null +++ b/diff/testdata/start.txt @@ -0,0 +1,34 @@ +-- old -- +e +pi +4 +5 +6 +7 +8 +9 +10 +-- new -- +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +-- diff -- +diff old new +--- old ++++ new +@@ -1,5 +1,6 @@ +-e +-pi ++1 ++2 ++3 + 4 + 5 + 6 diff --git a/diff/testdata/triv.txt b/diff/testdata/triv.txt new file mode 100644 index 00000000..ab5759fc --- /dev/null +++ b/diff/testdata/triv.txt @@ -0,0 +1,40 @@ +Another example from Hunt and McIlroy, +“An Algorithm for Differential File Comparison.” +https://www.cs.dartmouth.edu/~doug/diff.pdf + +Anchored diff gives up on finding anything, +since there are no unique lines. + +-- old -- +a +b +c +a +b +b +a +-- new -- +c +a +b +a +b +c +-- diff -- +diff old new +--- old ++++ new +@@ -1,7 +1,6 @@ +-a +-b +-c +-a +-b +-b +-a ++c ++a ++b ++a ++b ++c diff --git a/fmtsort/mapelem.go b/fmtsort/mapelem.go index 4e4c2945..98e4e38f 100644 --- a/fmtsort/mapelem.go +++ b/fmtsort/mapelem.go @@ -1,6 +1,3 @@ -//go:build go1.12 -// +build go1.12 - package fmtsort import "reflect" diff --git a/fmtsort/mapelem_1.11.go b/fmtsort/mapelem_1.11.go deleted file mode 100644 index 873bf7f5..00000000 --- a/fmtsort/mapelem_1.11.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !go1.12 -// +build !go1.12 - -package fmtsort - -import "reflect" - -const brokenNaNs = true - -func mapElems(mapValue reflect.Value) ([]reflect.Value, []reflect.Value) { - key := mapValue.MapKeys() - value := make([]reflect.Value, 0, len(key)) - for _, k := range key { - v := mapValue.MapIndex(k) - if !v.IsValid() { - // Note: we can't retrieve the value, probably because - // the key is NaN, so just do the best we can and - // add a zero value of the correct type in that case. - v = reflect.Zero(mapValue.Type().Elem()) - } - value = append(value, v) - } - return key, value -} diff --git a/fmtsort/sort.go b/fmtsort/sort.go index 0fb5187d..7f518541 100644 --- a/fmtsort/sort.go +++ b/fmtsort/sort.go @@ -36,19 +36,18 @@ func (o *SortedMap) Swap(i, j int) { // // The ordering rules are more general than with Go's < operator: // -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. -// +// - when applicable, nil compares low +// - ints, floats, and strings order by < +// - NaN compares less than non-NaN floats +// - bool compares false before true +// - complex compares real, then imag +// - pointers compare by machine address +// - channel values compare by machine address +// - structs compare each field in turn +// - arrays compare each element in turn. +// Otherwise identical arrays compare by length. +// - interface values compare first by reflect.Type describing the concrete type +// and then by concrete value as described in the previous rules. func Sort(mapValue reflect.Value) *SortedMap { if mapValue.Type().Kind() != reflect.Map { return nil diff --git a/go.mod b/go.mod index 3da36bc0..ceb68c2b 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,9 @@ module github.com/rogpeppe/go-internal -go 1.17 +go 1.20 -require github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e +require ( + golang.org/x/mod v0.9.0 + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f + golang.org/x/tools v0.1.12 +) diff --git a/go.sum b/go.sum index b8ef2fa4..d8a340c0 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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= diff --git a/goproxytest/proxy.go b/goproxytest/proxy.go index 05587603..070c7ee2 100644 --- a/goproxytest/proxy.go +++ b/goproxytest/proxy.go @@ -26,7 +26,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io/fs" "log" "net" "net/http" @@ -34,10 +34,11 @@ import ( "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,18 +91,18 @@ 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 } @@ -109,13 +110,13 @@ func (srv *Server) readModList() error { 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 } diff --git a/goproxytest/pseudo.go b/goproxytest/pseudo.go index 570acaae..3ff0c1f3 100644 --- a/goproxytest/pseudo.go +++ b/goproxytest/pseudo.go @@ -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 diff --git a/goproxytest/testdata/list.txt b/goproxytest/testdata/list.txt index 8b05a178..d9b7da9e 100644 --- a/goproxytest/testdata/list.txt +++ b/goproxytest/testdata/list.txt @@ -1,6 +1,3 @@ -# prior to go 1.12 you cannot list a module without a requirement -[!go1.12] go get fruit.com - go list -m -versions fruit.com stdout 'v1.0.0 v1.1.0' diff --git a/gotooltest/setup.go b/gotooltest/setup.go index 7e56df6e..c8d9ec07 100644 --- a/gotooltest/setup.go +++ b/gotooltest/setup.go @@ -10,6 +10,7 @@ import ( "bytes" "encoding/json" "fmt" + "os" "os/exec" "path/filepath" "regexp" @@ -36,12 +37,30 @@ var ( // initGoEnv initialises goEnv. It should only be called using goEnv.once.Do, // as in Setup. -func initGoEnv() error { - var err error +// +// Run all of these probe commands in a temporary directory, so as not to make +// any assumptions about the caller's working directory. +func initGoEnv() (err error) { + td, err := os.MkdirTemp("", "gotooltest-initGoEnv") + if err != nil { + return fmt.Errorf("failed to create temporary directory for go command tests: %w", err) + } + defer func() { + if rerr := os.RemoveAll(td); rerr != nil && err == nil { + err = fmt.Errorf("failed to remove temporary directory for go command tests: %w", rerr) + } + }() + + // Write a temporary go.mod file in td. This ensures that we create + // a porcelain environment in which to run these probe commands. + if err := os.WriteFile(filepath.Join(td, "go.mod"), []byte("module gotooltest"), 0600); err != nil { + return fmt.Errorf("failed to write temporary go.mod file: %w", err) + } run := func(args ...string) (*bytes.Buffer, *bytes.Buffer, error) { var stdout, stderr bytes.Buffer cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = td cmd.Stdout = &stdout cmd.Stderr = &stderr return &stdout, &stderr, cmd.Run() @@ -153,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, "") -} diff --git a/gotooltest/setup_test.go b/gotooltest/setup_test.go new file mode 100644 index 00000000..c8fc2976 --- /dev/null +++ b/gotooltest/setup_test.go @@ -0,0 +1,30 @@ +package gotooltest + +import ( + "os" + "testing" +) + +func TestInitGoEnv(t *testing.T) { + // Set up a temp directory containing a bad go.mod file to + // ensure the working directory does not influence the probe + // commands run during initGoEnv + td := t.TempDir() + + // If these commands fail we are in bigger trouble + wd, _ := os.Getwd() + os.Chdir(td) + + t.Cleanup(func() { + os.Chdir(wd) + os.Remove(td) + }) + + if err := os.WriteFile("go.mod", []byte("this is rubbish"), 0600); err != nil { + t.Fatal(err) + } + + if err := initGoEnv(); err != nil { + t.Fatal(err) + } +} diff --git a/gotooltest/testdata/cover.txt b/gotooltest/testdata/cover.txt index b3d13e26..815f4e65 100644 --- a/gotooltest/testdata/cover.txt +++ b/gotooltest/testdata/cover.txt @@ -1,7 +1,5 @@ unquote scripts/exec.txt -[darwin] skip 'Pending a fix for github.com/rogpeppe/go-internal/issues/130' - # The module uses testscript itself. # Use the checked out module, based on where the test binary ran. go mod edit -replace=github.com/rogpeppe/go-internal=${GOINTERNAL_MODULE} @@ -10,18 +8,14 @@ go mod tidy # First, a 'go test' run without coverage. go test -vet=off stdout 'PASS' -! stdout 'total coverage' +! stdout 'coverage' # Then, a 'go test' run with -coverprofile. -# Assuming testscript works well, this results in the basic coverage being 0%, -# since the test binary does not directly run any non-test code. -# The total coverage after merging profiles should end up being 100%, -# as long as all three sub-profiles are accounted for. +# The total coverage after merging profiles should end up being 100%. # Marking all printlns as covered requires all edge cases to work well. go test -vet=off -coverprofile=cover.out -v stdout 'PASS' -stdout '^coverage: 0\.0%' -stdout '^total coverage: 100\.0%' +stdout 'coverage: 100\.0%' ! stdout 'malformed coverage' # written by "go test" if cover.out is invalid exists cover.out diff --git a/imports/build.go b/imports/build.go index 5fd0106e..891295c9 100644 --- a/imports/build.go +++ b/imports/build.go @@ -32,7 +32,6 @@ var slashslash = []byte("//") // the purpose of satisfying build tags, in order to estimate // (conservatively) whether a file could ever possibly be used // in any build. -// func ShouldBuild(content []byte, tags map[string]bool) bool { // Pass 1. Identify leading run of // comments and blank lines, // which must be followed by a blank line. @@ -96,7 +95,6 @@ func ShouldBuild(content []byte, tags map[string]bool) bool { // tag (if tags[tag] is true) // !tag (if tags[tag] is false) // a comma-separated list of any of these -// func matchTags(name string, tags map[string]bool) bool { if name == "" { return false @@ -145,12 +143,12 @@ func matchTag(name string, tags map[string]bool, want bool) bool { // suffix which does not match the current system. // The recognized name formats are: // -// name_$(GOOS).* -// name_$(GOARCH).* -// name_$(GOOS)_$(GOARCH).* -// name_$(GOOS)_test.* -// name_$(GOARCH)_test.* -// name_$(GOOS)_$(GOARCH)_test.* +// name_$(GOOS).* +// name_$(GOARCH).* +// name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* // // An exception: if GOOS=android, then files with GOOS=linux are also matched. // diff --git a/internal/misspell/misspell.go b/internal/misspell/misspell.go new file mode 100644 index 00000000..6e1ae95e --- /dev/null +++ b/internal/misspell/misspell.go @@ -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 +} diff --git a/internal/misspell/misspell_test.go b/internal/misspell/misspell_test.go new file mode 100644 index 00000000..690006e7 --- /dev/null +++ b/internal/misspell/misspell_test.go @@ -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 +} diff --git a/internal/misspell/testdata/fuzz/FuzzAlmostEqual/295b316649ae86dd b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/295b316649ae86dd new file mode 100644 index 00000000..ce1515b2 --- /dev/null +++ b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/295b316649ae86dd @@ -0,0 +1,3 @@ +go test fuzz v1 +string("") +string("00") diff --git a/internal/misspell/testdata/fuzz/FuzzAlmostEqual/5bd9cd4e8c887808 b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/5bd9cd4e8c887808 new file mode 100644 index 00000000..8aaf0cf7 --- /dev/null +++ b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/5bd9cd4e8c887808 @@ -0,0 +1,3 @@ +go test fuzz v1 +string("\x980") +string("0\xb70") diff --git a/internal/misspell/testdata/fuzz/FuzzAlmostEqual/b323cef1fc26e507 b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/b323cef1fc26e507 new file mode 100644 index 00000000..e2e74e13 --- /dev/null +++ b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/b323cef1fc26e507 @@ -0,0 +1,3 @@ +go test fuzz v1 +string("OOOOOOOO000") +string("0000000000000") diff --git a/internal/misspell/testdata/fuzz/FuzzAlmostEqual/c6edde4256d6f5eb b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/c6edde4256d6f5eb new file mode 100644 index 00000000..4fcd6936 --- /dev/null +++ b/internal/misspell/testdata/fuzz/FuzzAlmostEqual/c6edde4256d6f5eb @@ -0,0 +1,3 @@ +go test fuzz v1 +string("OOOOOOOO000") +string("0000000000\x1000") diff --git a/internal/os/execpath/lp_js.go b/internal/os/execpath/lp_js.go index e826c319..a0571b71 100644 --- a/internal/os/execpath/lp_js.go +++ b/internal/os/execpath/lp_js.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build js && wasm -// +build js,wasm package execpath diff --git a/internal/os/execpath/lp_unix.go b/internal/os/execpath/lp_unix.go index cb8e9760..37f18c79 100644 --- a/internal/os/execpath/lp_unix.go +++ b/internal/os/execpath/lp_unix.go @@ -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 diff --git a/internal/syscall/windows/registry/key.go b/internal/syscall/windows/registry/key.go deleted file mode 100644 index fb89e39a..00000000 --- a/internal/syscall/windows/registry/key.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2015 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. - -//go:build windows -// +build windows - -// Package registry provides access to the Windows registry. -// -// Here is a simple example, opening a registry key and reading a string value from it. -// -// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) -// if err != nil { -// log.Fatal(err) -// } -// defer k.Close() -// -// s, _, err := k.GetStringValue("SystemRoot") -// if err != nil { -// log.Fatal(err) -// } -// fmt.Printf("Windows system root is %q\n", s) -// -// NOTE: This package is a copy of golang.org/x/sys/windows/registry -// with KeyInfo.ModTime removed to prevent dependency cycles. -// -package registry - -import ( - "io" - "syscall" -) - -const ( - // Registry key security and access rights. - // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx - // for details. - ALL_ACCESS = 0xf003f - CREATE_LINK = 0x00020 - CREATE_SUB_KEY = 0x00004 - ENUMERATE_SUB_KEYS = 0x00008 - EXECUTE = 0x20019 - NOTIFY = 0x00010 - QUERY_VALUE = 0x00001 - READ = 0x20019 - SET_VALUE = 0x00002 - WOW64_32KEY = 0x00200 - WOW64_64KEY = 0x00100 - WRITE = 0x20006 -) - -// Key is a handle to an open Windows registry key. -// Keys can be obtained by calling OpenKey; there are -// also some predefined root keys such as CURRENT_USER. -// Keys can be used directly in the Windows API. -type Key syscall.Handle - -const ( - // Windows defines some predefined root keys that are always open. - // An application can use these keys as entry points to the registry. - // Normally these keys are used in OpenKey to open new keys, - // but they can also be used anywhere a Key is required. - CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT) - CURRENT_USER = Key(syscall.HKEY_CURRENT_USER) - LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE) - USERS = Key(syscall.HKEY_USERS) - CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG) -) - -// Close closes open key k. -func (k Key) Close() error { - return syscall.RegCloseKey(syscall.Handle(k)) -} - -// OpenKey opens a new key with path name relative to key k. -// It accepts any open key, including CURRENT_USER and others, -// and returns the new key and an error. -// The access parameter specifies desired access rights to the -// key to be opened. -func OpenKey(k Key, path string, access uint32) (Key, error) { - p, err := syscall.UTF16PtrFromString(path) - if err != nil { - return 0, err - } - var subkey syscall.Handle - err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey) - if err != nil { - return 0, err - } - return Key(subkey), nil -} - -// ReadSubKeyNames returns the names of subkeys of key k. -// The parameter n controls the number of returned names, -// analogous to the way os.File.Readdirnames works. -func (k Key) ReadSubKeyNames(n int) ([]string, error) { - names := make([]string, 0) - // Registry key size limit is 255 bytes and described there: - // https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx - buf := make([]uint16, 256) //plus extra room for terminating zero byte -loopItems: - for i := uint32(0); ; i++ { - if n > 0 { - if len(names) == n { - return names, nil - } - } - l := uint32(len(buf)) - for { - err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) - if err == nil { - break - } - if err == syscall.ERROR_MORE_DATA { - // Double buffer size and try again. - l = uint32(2 * len(buf)) - buf = make([]uint16, l) - continue - } - if err == _ERROR_NO_MORE_ITEMS { - break loopItems - } - return names, err - } - names = append(names, syscall.UTF16ToString(buf[:l])) - } - if n > len(names) { - return names, io.EOF - } - return names, nil -} - -// CreateKey creates a key named path under open key k. -// CreateKey returns the new key and a boolean flag that reports -// whether the key already existed. -// The access parameter specifies the access rights for the key -// to be created. -func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) { - var h syscall.Handle - var d uint32 - err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path), - 0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d) - if err != nil { - return 0, false, err - } - return Key(h), d == _REG_OPENED_EXISTING_KEY, nil -} - -// DeleteKey deletes the subkey path of key k and its values. -func DeleteKey(k Key, path string) error { - return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path)) -} - -// A KeyInfo describes the statistics of a key. It is returned by Stat. -type KeyInfo struct { - SubKeyCount uint32 - MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte - ValueCount uint32 - MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte - MaxValueLen uint32 // longest data component among the key's values, in bytes - lastWriteTime syscall.Filetime -} - -// Stat retrieves information about the open key k. -func (k Key) Stat() (*KeyInfo, error) { - var ki KeyInfo - err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil, - &ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount, - &ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime) - if err != nil { - return nil, err - } - return &ki, nil -} diff --git a/internal/syscall/windows/registry/mksyscall.go b/internal/syscall/windows/registry/mksyscall.go deleted file mode 100644 index 07721535..00000000 --- a/internal/syscall/windows/registry/mksyscall.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2016 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 registry - -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go diff --git a/internal/syscall/windows/registry/syscall.go b/internal/syscall/windows/registry/syscall.go deleted file mode 100644 index bb612793..00000000 --- a/internal/syscall/windows/registry/syscall.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015 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. - -//go:build windows -// +build windows - -package registry - -import "syscall" - -const ( - _REG_OPTION_NON_VOLATILE = 0 - - _REG_CREATED_NEW_KEY = 1 - _REG_OPENED_EXISTING_KEY = 2 - - _ERROR_NO_MORE_ITEMS syscall.Errno = 259 -) - -func LoadRegLoadMUIString() error { - return procRegLoadMUIStringW.Find() -} - -//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW -//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW -//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW -//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW -//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW -//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW - -//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW diff --git a/internal/syscall/windows/registry/value.go b/internal/syscall/windows/registry/value.go deleted file mode 100644 index b89bfd82..00000000 --- a/internal/syscall/windows/registry/value.go +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2015 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. - -//go:build windows -// +build windows - -package registry - -import ( - "errors" - "io" - "syscall" - "unicode/utf16" - "unsafe" -) - -const ( - // Registry value types. - NONE = 0 - SZ = 1 - EXPAND_SZ = 2 - BINARY = 3 - DWORD = 4 - DWORD_BIG_ENDIAN = 5 - LINK = 6 - MULTI_SZ = 7 - RESOURCE_LIST = 8 - FULL_RESOURCE_DESCRIPTOR = 9 - RESOURCE_REQUIREMENTS_LIST = 10 - QWORD = 11 -) - -var ( - // ErrShortBuffer is returned when the buffer was too short for the operation. - ErrShortBuffer = syscall.ERROR_MORE_DATA - - // ErrNotExist is returned when a registry key or value does not exist. - ErrNotExist = syscall.ERROR_FILE_NOT_FOUND - - // ErrUnexpectedType is returned by Get*Value when the value's type was unexpected. - ErrUnexpectedType = errors.New("unexpected key value type") -) - -// GetValue retrieves the type and data for the specified value associated -// with an open key k. It fills up buffer buf and returns the retrieved -// byte count n. If buf is too small to fit the stored value it returns -// ErrShortBuffer error along with the required buffer size n. -// If no buffer is provided, it returns true and actual buffer size n. -// If no buffer is provided, GetValue returns the value's type only. -// If the value does not exist, the error returned is ErrNotExist. -// -// GetValue is a low level function. If value's type is known, use the appropriate -// Get*Value function instead. -func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) { - pname, err := syscall.UTF16PtrFromString(name) - if err != nil { - return 0, 0, err - } - var pbuf *byte - if len(buf) > 0 { - pbuf = (*byte)(unsafe.Pointer(&buf[0])) - } - l := uint32(len(buf)) - err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l) - if err != nil { - return int(l), valtype, err - } - return int(l), valtype, nil -} - -func (k Key) getValue(name string, buf []byte) (date []byte, valtype uint32, err error) { - p, err := syscall.UTF16PtrFromString(name) - if err != nil { - return nil, 0, err - } - var t uint32 - n := uint32(len(buf)) - for { - err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n) - if err == nil { - return buf[:n], t, nil - } - if err != syscall.ERROR_MORE_DATA { - return nil, 0, err - } - if n <= uint32(len(buf)) { - return nil, 0, err - } - buf = make([]byte, n) - } -} - -// GetStringValue retrieves the string value for the specified -// value name associated with an open key k. It also returns the value's type. -// If value does not exist, GetStringValue returns ErrNotExist. -// If value is not SZ or EXPAND_SZ, it will return the correct value -// type and ErrUnexpectedType. -func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) { - data, typ, err2 := k.getValue(name, make([]byte, 64)) - if err2 != nil { - return "", typ, err2 - } - switch typ { - case SZ, EXPAND_SZ: - default: - return "", typ, ErrUnexpectedType - } - if len(data) == 0 { - return "", typ, nil - } - u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:] - return syscall.UTF16ToString(u), typ, nil -} - -// GetMUIStringValue retrieves the localized string value for -// the specified value name associated with an open key k. -// If the value name doesn't exist or the localized string value -// can't be resolved, GetMUIStringValue returns ErrNotExist. -// GetMUIStringValue panics if the system doesn't support -// regLoadMUIString; use LoadRegLoadMUIString to check if -// regLoadMUIString is supported before calling this function. -func (k Key) GetMUIStringValue(name string) (string, error) { - pname, err := syscall.UTF16PtrFromString(name) - if err != nil { - return "", err - } - - buf := make([]uint16, 1024) - var buflen uint32 - var pdir *uint16 - - err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) - if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path - - // Try to resolve the string value using the system directory as - // a DLL search path; this assumes the string value is of the form - // @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320. - - // This approach works with tzres.dll but may have to be revised - // in the future to allow callers to provide custom search paths. - - var s string - s, err = ExpandString("%SystemRoot%\\system32\\") - if err != nil { - return "", err - } - pdir, err = syscall.UTF16PtrFromString(s) - if err != nil { - return "", err - } - - err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) - } - - for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed - if buflen <= uint32(len(buf)) { - break // Buffer not growing, assume race; break - } - buf = make([]uint16, buflen) - err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) - } - - if err != nil { - return "", err - } - - return syscall.UTF16ToString(buf), nil -} - -// ExpandString expands environment-variable strings and replaces -// them with the values defined for the current user. -// Use ExpandString to expand EXPAND_SZ strings. -func ExpandString(value string) (string, error) { - if value == "" { - return "", nil - } - p, err := syscall.UTF16PtrFromString(value) - if err != nil { - return "", err - } - r := make([]uint16, 100) - for { - n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r))) - if err != nil { - return "", err - } - if n <= uint32(len(r)) { - u := (*[1 << 29]uint16)(unsafe.Pointer(&r[0]))[:] - return syscall.UTF16ToString(u), nil - } - r = make([]uint16, n) - } -} - -// GetStringsValue retrieves the []string value for the specified -// value name associated with an open key k. It also returns the value's type. -// If value does not exist, GetStringsValue returns ErrNotExist. -// If value is not MULTI_SZ, it will return the correct value -// type and ErrUnexpectedType. -func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) { - data, typ, err2 := k.getValue(name, make([]byte, 64)) - if err2 != nil { - return nil, typ, err2 - } - if typ != MULTI_SZ { - return nil, typ, ErrUnexpectedType - } - if len(data) == 0 { - return nil, typ, nil - } - p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:len(data)/2] - if len(p) == 0 { - return nil, typ, nil - } - if p[len(p)-1] == 0 { - p = p[:len(p)-1] // remove terminating null - } - val = make([]string, 0, 5) - from := 0 - for i, c := range p { - if c == 0 { - val = append(val, string(utf16.Decode(p[from:i]))) - from = i + 1 - } - } - return val, typ, nil -} - -// GetIntegerValue retrieves the integer value for the specified -// value name associated with an open key k. It also returns the value's type. -// If value does not exist, GetIntegerValue returns ErrNotExist. -// If value is not DWORD or QWORD, it will return the correct value -// type and ErrUnexpectedType. -func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) { - data, typ, err2 := k.getValue(name, make([]byte, 8)) - if err2 != nil { - return 0, typ, err2 - } - switch typ { - case DWORD: - if len(data) != 4 { - return 0, typ, errors.New("DWORD value is not 4 bytes long") - } - return uint64(*(*uint32)(unsafe.Pointer(&data[0]))), DWORD, nil - case QWORD: - if len(data) != 8 { - return 0, typ, errors.New("QWORD value is not 8 bytes long") - } - return uint64(*(*uint64)(unsafe.Pointer(&data[0]))), QWORD, nil - default: - return 0, typ, ErrUnexpectedType - } -} - -// GetBinaryValue retrieves the binary value for the specified -// value name associated with an open key k. It also returns the value's type. -// If value does not exist, GetBinaryValue returns ErrNotExist. -// If value is not BINARY, it will return the correct value -// type and ErrUnexpectedType. -func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) { - data, typ, err2 := k.getValue(name, make([]byte, 64)) - if err2 != nil { - return nil, typ, err2 - } - if typ != BINARY { - return nil, typ, ErrUnexpectedType - } - return data, typ, nil -} - -func (k Key) setValue(name string, valtype uint32, data []byte) error { - p, err := syscall.UTF16PtrFromString(name) - if err != nil { - return err - } - if len(data) == 0 { - return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0) - } - return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data))) -} - -// SetDWordValue sets the data and type of a name value -// under key k to value and DWORD. -func (k Key) SetDWordValue(name string, value uint32) error { - return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:]) -} - -// SetQWordValue sets the data and type of a name value -// under key k to value and QWORD. -func (k Key) SetQWordValue(name string, value uint64) error { - return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:]) -} - -func (k Key) setStringValue(name string, valtype uint32, value string) error { - v, err := syscall.UTF16FromString(value) - if err != nil { - return err - } - buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] - return k.setValue(name, valtype, buf) -} - -// SetStringValue sets the data and type of a name value -// under key k to value and SZ. The value must not contain a zero byte. -func (k Key) SetStringValue(name, value string) error { - return k.setStringValue(name, SZ, value) -} - -// SetExpandStringValue sets the data and type of a name value -// under key k to value and EXPAND_SZ. The value must not contain a zero byte. -func (k Key) SetExpandStringValue(name, value string) error { - return k.setStringValue(name, EXPAND_SZ, value) -} - -// SetStringsValue sets the data and type of a name value -// under key k to value and MULTI_SZ. The value strings -// must not contain a zero byte. -func (k Key) SetStringsValue(name string, value []string) error { - ss := "" - for _, s := range value { - for i := 0; i < len(s); i++ { - if s[i] == 0 { - return errors.New("string cannot have 0 inside") - } - } - ss += s + "\x00" - } - v := utf16.Encode([]rune(ss + "\x00")) - buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] - return k.setValue(name, MULTI_SZ, buf) -} - -// SetBinaryValue sets the data and type of a name value -// under key k to value and BINARY. -func (k Key) SetBinaryValue(name string, value []byte) error { - return k.setValue(name, BINARY, value) -} - -// DeleteValue removes a named value from the key k. -func (k Key) DeleteValue(name string) error { - return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name)) -} - -// ReadValueNames returns the value names of key k. -// The parameter n controls the number of returned names, -// analogous to the way os.File.Readdirnames works. -func (k Key) ReadValueNames(n int) ([]string, error) { - ki, err := k.Stat() - if err != nil { - return nil, err - } - names := make([]string, 0, ki.ValueCount) - buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character -loopItems: - for i := uint32(0); ; i++ { - if n > 0 { - if len(names) == n { - return names, nil - } - } - l := uint32(len(buf)) - for { - err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) - if err == nil { - break - } - if err == syscall.ERROR_MORE_DATA { - // Double buffer size and try again. - l = uint32(2 * len(buf)) - buf = make([]uint16, l) - continue - } - if err == _ERROR_NO_MORE_ITEMS { - break loopItems - } - return names, err - } - names = append(names, syscall.UTF16ToString(buf[:l])) - } - if n > len(names) { - return names, io.EOF - } - return names, nil -} diff --git a/internal/syscall/windows/registry/zsyscall_windows.go b/internal/syscall/windows/registry/zsyscall_windows.go deleted file mode 100644 index ba1fc7d0..00000000 --- a/internal/syscall/windows/registry/zsyscall_windows.go +++ /dev/null @@ -1,111 +0,0 @@ -// Code generated by 'go generate'; DO NOT EDIT. - -package registry - -import ( - "syscall" - "unsafe" - - "github.com/rogpeppe/go-internal/internal/syscall/windows/sysdll" -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e syscall.Errno) error { - switch e { - case 0: - return nil - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - // TODO: add more here, after collecting data on the common - // error values see on Windows. (perhaps when running - // all.bat?) - return e -} - -var ( - modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll")) - modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll")) - - procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW") - procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW") - procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") - procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") - procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") - procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW") - procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") -) - -func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) { - r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition))) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) { - r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) { - r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize)) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { - r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { - r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) { - r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0) - if r0 != 0 { - regerrno = syscall.Errno(r0) - } - return -} - -func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { - r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) - n = uint32(r0) - if n == 0 { - if e1 != 0 { - err = errnoErr(e1) - } else { - err = syscall.EINVAL - } - } - return -} diff --git a/internal/textutil/diff.go b/internal/textutil/diff.go deleted file mode 100644 index 59f9e01a..00000000 --- a/internal/textutil/diff.go +++ /dev/null @@ -1,78 +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 textutil - -import ( - "fmt" - "strings" -) - -// Diff returns a formatted diff of the two texts, -// showing the entire text and the minimum line-level -// additions and removals to turn text1 into text2. -// (That is, lines only in text1 appear with a leading -, -// and lines only in text2 appear with a leading +.) -func Diff(text1, text2 string) string { - if text1 != "" && !strings.HasSuffix(text1, "\n") { - text1 += "(missing final newline)" - } - lines1 := strings.Split(text1, "\n") - lines1 = lines1[:len(lines1)-1] // remove empty string after final line - if text2 != "" && !strings.HasSuffix(text2, "\n") { - text2 += "(missing final newline)" - } - lines2 := strings.Split(text2, "\n") - lines2 = lines2[:len(lines2)-1] // remove empty string after final line - - // Naive dynamic programming algorithm for edit distance. - // https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm - // dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j] - // (The reversed indices make following the minimum cost path - // visit lines in the same order as in the text.) - dist := make([][]int, len(lines1)+1) - for i := range dist { - dist[i] = make([]int, len(lines2)+1) - if i == 0 { - for j := range dist[0] { - dist[0][j] = j - } - continue - } - for j := range dist[i] { - if j == 0 { - dist[i][0] = i - continue - } - cost := dist[i][j-1] + 1 - if cost > dist[i-1][j]+1 { - cost = dist[i-1][j] + 1 - } - if lines1[len(lines1)-i] == lines2[len(lines2)-j] { - if cost > dist[i-1][j-1] { - cost = dist[i-1][j-1] - } - } - dist[i][j] = cost - } - } - - var buf strings.Builder - i, j := len(lines1), len(lines2) - for i > 0 || j > 0 { - cost := dist[i][j] - if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] { - fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i]) - i-- - j-- - } else if i > 0 && cost == dist[i-1][j]+1 { - fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i]) - i-- - } else { - fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j]) - j-- - } - } - return buf.String() -} diff --git a/internal/textutil/diff_test.go b/internal/textutil/diff_test.go deleted file mode 100644 index 48cd9e94..00000000 --- a/internal/textutil/diff_test.go +++ /dev/null @@ -1,45 +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 textutil_test - -import ( - "strings" - "testing" - - "github.com/rogpeppe/go-internal/internal/textutil" -) - -var diffTests = []struct { - text1 string - text2 string - diff string -}{ - {"a b c", "a b d e f", "a b -c +d +e +f"}, - {"", "a b c", "+a +b +c"}, - {"a b c", "", "-a -b -c"}, - {"a b c", "d e f", "-a -b -c +d +e +f"}, - {"a b c d e f", "a b d e f", "a b -c d e f"}, - {"a b c e f", "a b c d e f", "a b c +d e f"}, -} - -func TestDiff(t *testing.T) { - for _, tt := range diffTests { - // Turn spaces into \n. - text1 := strings.Replace(tt.text1, " ", "\n", -1) - if text1 != "" { - text1 += "\n" - } - text2 := strings.Replace(tt.text2, " ", "\n", -1) - if text2 != "" { - text2 += "\n" - } - out := textutil.Diff(text1, text2) - // Cut final \n, cut spaces, turn remaining \n into spaces. - out = strings.Replace(strings.Replace(strings.TrimSuffix(out, "\n"), " ", "", -1), "\n", " ", -1) - if out != tt.diff { - t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff) - } - } -} diff --git a/internal/textutil/doc.go b/internal/textutil/doc.go deleted file mode 100644 index f4a041a7..00000000 --- a/internal/textutil/doc.go +++ /dev/null @@ -1,9 +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 textutil contains text processing utilities. -// -// This package came to life as a result of refactoring code common to -// internal packages we have factored out of the Go repo. -package textutil diff --git a/lockedfile/internal/filelock/filelock.go b/lockedfile/internal/filelock/filelock.go index aba3eed7..05f27c32 100644 --- a/lockedfile/internal/filelock/filelock.go +++ b/lockedfile/internal/filelock/filelock.go @@ -9,6 +9,7 @@ package filelock import ( "errors" + "io/fs" "os" ) @@ -24,7 +25,7 @@ type File interface { Fd() uintptr // Stat returns the FileInfo structure describing file. - Stat() (os.FileInfo, error) + Stat() (fs.FileInfo, error) } // Lock places an advisory write lock on the file, blocking until it can be @@ -87,7 +88,7 @@ var ErrNotSupported = errors.New("operation not supported") // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { - case *os.PathError: + case *fs.PathError: return err.Err case *os.LinkError: return err.Err diff --git a/lockedfile/internal/filelock/filelock_fcntl.go b/lockedfile/internal/filelock/filelock_fcntl.go index 4e644811..85680485 100644 --- a/lockedfile/internal/filelock/filelock_fcntl.go +++ b/lockedfile/internal/filelock/filelock_fcntl.go @@ -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 || solaris -// +build aix solaris +//go: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 @@ -13,17 +12,14 @@ // Most platforms provide some alternative API, such as an 'flock' system call // or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and // does not require per-inode bookkeeping in the application. -// -// TODO(golang.org/issue/35618): add a syscall.Flock binding for Illumos and -// switch it over to use filelock_unix.go. package filelock import ( "errors" "io" + "io/fs" "math/rand" - "os" "sync" "syscall" "time" @@ -43,8 +39,6 @@ type inodeLock struct { queue []<-chan File } -type token struct{} - var ( mu sync.Mutex inodes = map[File]inode{} @@ -65,7 +59,7 @@ func lock(f File, lt lockType) (err error) { mu.Lock() if i, dup := inodes[f]; dup && i != ino { mu.Unlock() - return &os.PathError{ + return &fs.PathError{ Op: lt.String(), Path: f.Name(), Err: errors.New("inode for file changed since last Lock or RLock"), @@ -156,7 +150,7 @@ func lock(f File, lt lockType) (err error) { if err != nil { unlock(f) - return &os.PathError{ + return &fs.PathError{ Op: lt.String(), Path: f.Name(), Err: err, diff --git a/lockedfile/internal/filelock/filelock_other.go b/lockedfile/internal/filelock/filelock_other.go index cfc53386..7bdd62bd 100644 --- a/lockedfile/internal/filelock/filelock_other.go +++ b/lockedfile/internal/filelock/filelock_other.go @@ -2,12 +2,11 @@ // 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 -import "os" +import "io/fs" type lockType int8 @@ -17,7 +16,7 @@ const ( ) func lock(f File, lt lockType) error { - return &os.PathError{ + return &fs.PathError{ Op: lt.String(), Path: f.Name(), Err: ErrNotSupported, @@ -25,7 +24,7 @@ func lock(f File, lt lockType) error { } func unlock(f File) error { - return &os.PathError{ + return &fs.PathError{ Op: "Unlock", Path: f.Name(), Err: ErrNotSupported, diff --git a/lockedfile/internal/filelock/filelock_plan9.go b/lockedfile/internal/filelock/filelock_plan9.go deleted file mode 100644 index 5ae3cc28..00000000 --- a/lockedfile/internal/filelock/filelock_plan9.go +++ /dev/null @@ -1,39 +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. - -//go:build plan9 -// +build plan9 - -package filelock - -import ( - "os" -) - -type lockType int8 - -const ( - readLock = iota + 1 - writeLock -) - -func lock(f File, lt lockType) error { - return &os.PathError{ - Op: lt.String(), - Path: f.Name(), - Err: ErrNotSupported, - } -} - -func unlock(f File) error { - return &os.PathError{ - Op: "Unlock", - Path: f.Name(), - Err: ErrNotSupported, - } -} - -func isNotSupported(err error) bool { - return err == ErrNotSupported -} diff --git a/lockedfile/internal/filelock/filelock_test.go b/lockedfile/internal/filelock/filelock_test.go index 925f6417..429969c3 100644 --- a/lockedfile/internal/filelock/filelock_test.go +++ b/lockedfile/internal/filelock/filelock_test.go @@ -2,14 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !js && !nacl && !plan9 -// +build !js,!nacl,!plan9 +//go:build !js && !plan9 package filelock_test import ( "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -17,8 +15,6 @@ import ( "testing" "time" - "github.com/rogpeppe/go-internal/testenv" - "github.com/rogpeppe/go-internal/lockedfile/internal/filelock" ) @@ -53,9 +49,9 @@ func mustTempFile(t *testing.T) (f *os.File, remove func()) { t.Helper() base := filepath.Base(t.Name()) - f, err := ioutil.TempFile("", base) + f, err := os.CreateTemp("", base) if err != nil { - t.Fatalf(`ioutil.TempFile("", %q) = %v`, base, err) + t.Fatalf(`os.CreateTemp("", %q) = %v`, base, err) } t.Logf("fd %d = %s", f.Fd(), f.Name()) @@ -161,7 +157,9 @@ func TestRLockExcludesOnlyLock(t *testing.T) { f2 := mustOpen(t, f.Name()) defer f2.Close() - if runtime.GOOS == "solaris" || runtime.GOOS == "aix" { + doUnlockTF := false + switch runtime.GOOS { + case "aix", "solaris": // When using POSIX locks (as on Solaris), we can't safely read-lock the // same inode through two different descriptors at the same time: when the // first descriptor is closed, the second descriptor would still be open but @@ -169,8 +167,9 @@ func TestRLockExcludesOnlyLock(t *testing.T) { lockF2 := mustBlock(t, "RLock", f2) unlock(t, f) lockF2(t) - } else { + default: rLock(t, f2) + doUnlockTF = true } other := mustOpen(t, f.Name()) @@ -178,7 +177,7 @@ func TestRLockExcludesOnlyLock(t *testing.T) { lockOther := mustBlock(t, "Lock", other) unlock(t, f2) - if runtime.GOOS != "solaris" && runtime.GOOS != "aix" { + if doUnlockTF { unlock(t, f) } lockOther(t) @@ -186,8 +185,6 @@ func TestRLockExcludesOnlyLock(t *testing.T) { } func TestLockNotDroppedByExecCommand(t *testing.T) { - testenv.MustHaveExec(t) - f, remove := mustTempFile(t) defer remove() diff --git a/lockedfile/internal/filelock/filelock_unix.go b/lockedfile/internal/filelock/filelock_unix.go index 09549efd..d7778d05 100644 --- a/lockedfile/internal/filelock/filelock_unix.go +++ b/lockedfile/internal/filelock/filelock_unix.go @@ -2,13 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd -// +build darwin dragonfly freebsd linux netbsd openbsd +//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd package filelock import ( - "os" + "io/fs" "syscall" ) @@ -27,7 +26,7 @@ func lock(f File, lt lockType) (err error) { } } if err != nil { - return &os.PathError{ + return &fs.PathError{ Op: lt.String(), Path: f.Name(), Err: err, diff --git a/lockedfile/internal/filelock/filelock_windows.go b/lockedfile/internal/filelock/filelock_windows.go index 2bd3eb96..ceab65b0 100644 --- a/lockedfile/internal/filelock/filelock_windows.go +++ b/lockedfile/internal/filelock/filelock_windows.go @@ -3,12 +3,11 @@ // license that can be found in the LICENSE file. //go:build windows -// +build windows package filelock import ( - "os" + "io/fs" "syscall" "github.com/rogpeppe/go-internal/internal/syscall/windows" @@ -36,7 +35,7 @@ func lock(f File, lt lockType) error { err := windows.LockFileEx(syscall.Handle(f.Fd()), uint32(lt), reserved, allBytes, allBytes, ol) if err != nil { - return &os.PathError{ + return &fs.PathError{ Op: lt.String(), Path: f.Name(), Err: err, @@ -49,7 +48,7 @@ func unlock(f File) error { ol := new(syscall.Overlapped) err := windows.UnlockFileEx(syscall.Handle(f.Fd()), reserved, allBytes, allBytes, ol) if err != nil { - return &os.PathError{ + return &fs.PathError{ Op: "Unlock", Path: f.Name(), Err: err, diff --git a/lockedfile/lockedfile.go b/lockedfile/lockedfile.go index 88f95b42..82e1a896 100644 --- a/lockedfile/lockedfile.go +++ b/lockedfile/lockedfile.go @@ -9,7 +9,7 @@ package lockedfile import ( "fmt" "io" - "io/ioutil" + "io/fs" "os" "runtime" ) @@ -35,7 +35,7 @@ type osFile struct { // OpenFile is like os.OpenFile, but returns a locked file. // If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked; // otherwise, it is read-locked. -func OpenFile(name string, flag int, perm os.FileMode) (*File, error) { +func OpenFile(name string, flag int, perm fs.FileMode) (*File, error) { var ( f = new(File) err error @@ -64,16 +64,16 @@ func Open(name string) (*File, error) { // Create is like os.Create, but returns a write-locked file. func Create(name string) (*File, error) { - return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) } -// Edit creates the named file with mode 0o666 (before umask), +// Edit creates the named file with mode 0666 (before umask), // but does not truncate existing contents. // // If Edit succeeds, methods on the returned File can be used for I/O. // The associated file descriptor has mode O_RDWR and the file is write-locked. func Edit(name string) (*File, error) { - return OpenFile(name, os.O_RDWR|os.O_CREATE, 0o666) + return OpenFile(name, os.O_RDWR|os.O_CREATE, 0666) } // Close unlocks and closes the underlying file. @@ -82,10 +82,10 @@ func Edit(name string) (*File, error) { // non-nil error. func (f *File) Close() error { if f.closed { - return &os.PathError{ + return &fs.PathError{ Op: "close", Path: f.Name(), - Err: os.ErrClosed, + Err: fs.ErrClosed, } } f.closed = true @@ -103,12 +103,12 @@ func Read(name string) ([]byte, error) { } defer f.Close() - return ioutil.ReadAll(f) + return io.ReadAll(f) } // Write opens the named file (creating it with the given permissions if needed), // then write-locks it and overwrites it with the given content. -func Write(name string, content io.Reader, perm os.FileMode) (err error) { +func Write(name string, content io.Reader, perm fs.FileMode) (err error) { f, err := OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err @@ -120,3 +120,68 @@ func Write(name string, content io.Reader, perm os.FileMode) (err error) { } return err } + +// Transform invokes t with the result of reading the named file, with its lock +// still held. +// +// If t returns a nil error, Transform then writes the returned contents back to +// the file, making a best effort to preserve existing contents on error. +// +// t must not modify the slice passed to it. +func Transform(name string, t func([]byte) ([]byte, error)) (err error) { + f, err := Edit(name) + if err != nil { + return err + } + defer f.Close() + + old, err := io.ReadAll(f) + if err != nil { + return err + } + + new, err := t(old) + if err != nil { + return err + } + + if len(new) > len(old) { + // The overall file size is increasing, so write the tail first: if we're + // about to run out of space on the disk, we would rather detect that + // failure before we have overwritten the original contents. + if _, err := f.WriteAt(new[len(old):], int64(len(old))); err != nil { + // Make a best effort to remove the incomplete tail. + f.Truncate(int64(len(old))) + return err + } + } + + // We're about to overwrite the old contents. In case of failure, make a best + // effort to roll back before we close the file. + defer func() { + if err != nil { + if _, err := f.WriteAt(old, 0); err == nil { + f.Truncate(int64(len(old))) + } + } + }() + + if len(new) >= len(old) { + if _, err := f.WriteAt(new[:len(old)], 0); err != nil { + return err + } + } else { + if _, err := f.WriteAt(new, 0); err != nil { + return err + } + // The overall file size is decreasing, so shrink the file to its final size + // after writing. We do this after writing (instead of before) so that if + // the write fails, enough filesystem space will likely still be reserved + // to contain the previous contents. + if err := f.Truncate(int64(len(new))); err != nil { + return err + } + } + + return nil +} diff --git a/lockedfile/lockedfile_filelock.go b/lockedfile/lockedfile_filelock.go index 6a031739..454c3a42 100644 --- a/lockedfile/lockedfile_filelock.go +++ b/lockedfile/lockedfile_filelock.go @@ -3,17 +3,17 @@ // license that can be found in the LICENSE file. //go:build !plan9 -// +build !plan9 package lockedfile import ( + "io/fs" "os" "github.com/rogpeppe/go-internal/lockedfile/internal/filelock" ) -func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { +func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { // On BSD systems, we could add the O_SHLOCK or O_EXLOCK flag to the OpenFile // call instead of locking separately, but we have to support separate locking // calls for Linux and Windows anyway, so it's simpler to use that approach diff --git a/lockedfile/lockedfile_plan9.go b/lockedfile/lockedfile_plan9.go index 02221c56..a2ce794b 100644 --- a/lockedfile/lockedfile_plan9.go +++ b/lockedfile/lockedfile_plan9.go @@ -3,11 +3,11 @@ // license that can be found in the LICENSE file. //go:build plan9 -// +build plan9 package lockedfile import ( + "io/fs" "math/rand" "os" "strings" @@ -17,9 +17,9 @@ import ( // Opening an exclusive-use file returns an error. // The expected error strings are: // -// - "open/create -- file is locked" (cwfs, kfs) -// - "exclusive lock" (fossil) -// - "exclusive use file already open" (ramfs) +// - "open/create -- file is locked" (cwfs, kfs) +// - "exclusive lock" (fossil) +// - "exclusive use file already open" (ramfs) var lockedErrStrings = [...]string{ "file is locked", "exclusive lock", @@ -42,7 +42,7 @@ func isLocked(err error) bool { return false } -func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { +func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. // // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open @@ -57,8 +57,8 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { // have the ModeExclusive bit set. Set it before we call OpenFile, so that we // can be confident that a successful OpenFile implies exclusive use. if fi, err := os.Stat(name); err == nil { - if fi.Mode()&os.ModeExclusive == 0 { - if err := os.Chmod(name, fi.Mode()|os.ModeExclusive); err != nil { + if fi.Mode()&fs.ModeExclusive == 0 { + if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil { return nil, err } } @@ -69,7 +69,7 @@ func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { nextSleep := 1 * time.Millisecond const maxSleep = 500 * time.Millisecond for { - f, err := os.OpenFile(name, flag, perm|os.ModeExclusive) + f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive) if err == nil { return f, nil } diff --git a/lockedfile/lockedfile_test.go b/lockedfile/lockedfile_test.go index bc51de5b..e4e01dcb 100644 --- a/lockedfile/lockedfile_test.go +++ b/lockedfile/lockedfile_test.go @@ -2,30 +2,27 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// js and nacl do not support inter-process file locking. -//go:build !js && !nacl -// +build !js,!nacl +// js does not support inter-process file locking. +// +//go:build !js package lockedfile_test import ( "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" "testing" "time" - "github.com/rogpeppe/go-internal/testenv" - "github.com/rogpeppe/go-internal/lockedfile" ) func mustTempDir(t *testing.T) (dir string, remove func()) { t.Helper() - dir, err := ioutil.TempDir("", filepath.Base(t.Name())) + dir, err := os.MkdirTemp("", filepath.Base(t.Name())) if err != nil { t.Fatal(err) } @@ -46,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: + } - case <-time.After(quiescent): - return func(t *testing.T) { + return func(t *testing.T) { + logTimer := time.NewTimer(quiescent) + defer logTimer.Stop() + + 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: } } } @@ -157,8 +167,8 @@ func TestCanLockExistingFile(t *testing.T) { defer remove() path := filepath.Join(dir, "existing.txt") - if err := ioutil.WriteFile(path, []byte("ok"), 0o777); err != nil { - t.Fatalf("ioutil.WriteFile: %v", err) + if err := os.WriteFile(path, []byte("ok"), 0777); err != nil { + t.Fatalf("os.WriteFile: %v", err) } f, err := lockedfile.Edit(path) @@ -191,8 +201,6 @@ func TestSpuriousEDEADLK(t *testing.T) { // P.2 unblocks and locks file B. // P.2 unlocks file B. - testenv.MustHaveExec(t) - dirVar := t.Name() + "DIR" if dir := os.Getenv(dirVar); dir != "" { @@ -203,7 +211,7 @@ func TestSpuriousEDEADLK(t *testing.T) { } defer b.Close() - if err := ioutil.WriteFile(filepath.Join(dir, "locked"), []byte("ok"), 0o666); err != nil { + if err := os.WriteFile(filepath.Join(dir, "locked"), []byte("ok"), 0666); err != nil { t.Fatal(err) } @@ -247,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: } } diff --git a/lockedfile/mutex.go b/lockedfile/mutex.go index de3be57f..180a36c6 100644 --- a/lockedfile/mutex.go +++ b/lockedfile/mutex.go @@ -7,6 +7,7 @@ package lockedfile import ( "fmt" "os" + "sync" ) // A Mutex provides mutual exclusion within and across processes by locking a @@ -21,7 +22,8 @@ import ( // must not be copied after first use. The Path field must be set before first // use and must not be change thereafter. type Mutex struct { - Path string // The path to the well-known lock file. Must be non-empty. + Path string // The path to the well-known lock file. Must be non-empty. + mu sync.Mutex // A redundant mutex. The race detector doesn't know about file locking, so in tests we may need to lock something that it understands. } // MutexAt returns a new Mutex with Path set to the given non-empty path. @@ -52,9 +54,14 @@ func (mu *Mutex) Lock() (unlock func(), err error) { // in the future, it should call OpenFile with O_RDONLY and will require the // files must be readable, so we should not let the caller make any // assumptions about Mutex working with write-only files. - f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0o666) + f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0666) if err != nil { return nil, err } - return func() { f.Close() }, nil + mu.mu.Lock() + + return func() { + mu.mu.Unlock() + f.Close() + }, nil } diff --git a/lockedfile/transform_test.go b/lockedfile/transform_test.go new file mode 100644 index 00000000..f7488fa1 --- /dev/null +++ b/lockedfile/transform_test.go @@ -0,0 +1,105 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// js does not support inter-process file locking. +// +//go:build !js + +package lockedfile_test + +import ( + "bytes" + "encoding/binary" + "math/rand" + "path/filepath" + "testing" + "time" + + "github.com/rogpeppe/go-internal/lockedfile" +) + +func isPowerOf2(x int) bool { + return x > 0 && x&(x-1) == 0 +} + +func roundDownToPowerOf2(x int) int { + if x <= 0 { + panic("nonpositive x") + } + bit := 1 + for x != bit { + x = x &^ bit + bit <<= 1 + } + return x +} + +func TestTransform(t *testing.T) { + dir, remove := mustTempDir(t) + defer remove() + path := filepath.Join(dir, "blob.bin") + + const maxChunkWords = 8 << 10 + buf := make([]byte, 2*maxChunkWords*8) + for i := uint64(0); i < 2*maxChunkWords; i++ { + binary.LittleEndian.PutUint64(buf[i*8:], i) + } + if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil { + t.Fatal(err) + } + + var attempts int64 = 128 + if !testing.Short() { + attempts *= 16 + } + const parallel = 32 + + var sem = make(chan bool, parallel) + + for n := attempts; n > 0; n-- { + sem <- true + go func() { + defer func() { <-sem }() + + time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) + chunkWords := roundDownToPowerOf2(rand.Intn(maxChunkWords) + 1) + offset := rand.Intn(chunkWords) + + err := lockedfile.Transform(path, func(data []byte) (chunk []byte, err error) { + chunk = buf[offset*8 : (offset+chunkWords)*8] + + if len(data)&^7 != len(data) { + t.Errorf("read %d bytes, but each write is an integer multiple of 8 bytes", len(data)) + return chunk, nil + } + + words := len(data) / 8 + if !isPowerOf2(words) { + t.Errorf("read %d 8-byte words, but each write is a power-of-2 number of words", words) + return chunk, nil + } + + u := binary.LittleEndian.Uint64(data) + for i := 1; i < words; i++ { + next := binary.LittleEndian.Uint64(data[i*8:]) + if next != u+1 { + t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i) + return chunk, nil + } + u = next + } + + return chunk, nil + }) + + if err != nil { + t.Errorf("unexpected error from Transform: %v", err) + } + }() + } + + for n := parallel; n > 0; n-- { + sem <- true + } +} diff --git a/modfile/forward.go b/modfile/forward.go new file mode 100644 index 00000000..32b1d8f3 --- /dev/null +++ b/modfile/forward.go @@ -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) +} diff --git a/modfile/print.go b/modfile/print.go deleted file mode 100644 index 7b1dd8f9..00000000 --- a/modfile/print.go +++ /dev/null @@ -1,164 +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 modfile implements parsing and formatting for -// go.mod files. -package modfile - -import ( - "bytes" - "fmt" - "strings" -) - -func Format(f *FileSyntax) []byte { - pr := &printer{} - pr.file(f) - return pr.Bytes() -} - -// A printer collects the state during printing of a file or expression. -type printer struct { - bytes.Buffer // output buffer - comment []Comment // pending end-of-line comments - margin int // left margin (indent), a number of tabs -} - -// printf prints to the buffer. -func (p *printer) printf(format string, args ...interface{}) { - fmt.Fprintf(p, format, args...) -} - -// indent returns the position on the current line, in bytes, 0-indexed. -func (p *printer) indent() int { - b := p.Bytes() - n := 0 - for n < len(b) && b[len(b)-1-n] != '\n' { - n++ - } - return n -} - -// newline ends the current line, flushing end-of-line comments. -func (p *printer) newline() { - if len(p.comment) > 0 { - p.printf(" ") - for i, com := range p.comment { - if i > 0 { - p.trim() - p.printf("\n") - for i := 0; i < p.margin; i++ { - p.printf("\t") - } - } - p.printf("%s", strings.TrimSpace(com.Token)) - } - p.comment = p.comment[:0] - } - - p.trim() - p.printf("\n") - for i := 0; i < p.margin; i++ { - p.printf("\t") - } -} - -// trim removes trailing spaces and tabs from the current line. -func (p *printer) trim() { - // Remove trailing spaces and tabs from line we're about to end. - b := p.Bytes() - n := len(b) - for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') { - n-- - } - p.Truncate(n) -} - -// file formats the given file into the print buffer. -func (p *printer) file(f *FileSyntax) { - for _, com := range f.Before { - p.printf("%s", strings.TrimSpace(com.Token)) - p.newline() - } - - for i, stmt := range f.Stmt { - switch x := stmt.(type) { - case *CommentBlock: - // comments already handled - p.expr(x) - - default: - p.expr(x) - p.newline() - } - - for _, com := range stmt.Comment().After { - p.printf("%s", strings.TrimSpace(com.Token)) - p.newline() - } - - if i+1 < len(f.Stmt) { - p.newline() - } - } -} - -func (p *printer) expr(x Expr) { - // Emit line-comments preceding this expression. - if before := x.Comment().Before; len(before) > 0 { - // Want to print a line comment. - // Line comments must be at the current margin. - p.trim() - if p.indent() > 0 { - // There's other text on the line. Start a new line. - p.printf("\n") - } - // Re-indent to margin. - for i := 0; i < p.margin; i++ { - p.printf("\t") - } - for _, com := range before { - p.printf("%s", strings.TrimSpace(com.Token)) - p.newline() - } - } - - switch x := x.(type) { - default: - panic(fmt.Errorf("printer: unexpected type %T", x)) - - case *CommentBlock: - // done - - case *LParen: - p.printf("(") - case *RParen: - p.printf(")") - - case *Line: - sep := "" - for _, tok := range x.Token { - p.printf("%s%s", sep, tok) - sep = " " - } - - case *LineBlock: - for _, tok := range x.Token { - p.printf("%s ", tok) - } - p.expr(&x.LParen) - p.margin++ - for _, l := range x.Line { - p.newline() - p.expr(l) - } - p.margin-- - p.newline() - p.expr(&x.RParen) - } - - // Queue end-of-line comments for printing when we - // reach the end of the line. - p.comment = append(p.comment, x.Comment().Suffix...) -} diff --git a/modfile/read.go b/modfile/read.go deleted file mode 100644 index 1d81ff1a..00000000 --- a/modfile/read.go +++ /dev/null @@ -1,869 +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. - -// Module file parser. -// This is a simplified copy of Google's buildifier parser. - -package modfile - -import ( - "bytes" - "fmt" - "os" - "strconv" - "strings" - "unicode" - "unicode/utf8" -) - -// A Position describes the position between two bytes of input. -type Position struct { - Line int // line in input (starting at 1) - LineRune int // rune in line (starting at 1) - Byte int // byte in input (starting at 0) -} - -// add returns the position at the end of s, assuming it starts at p. -func (p Position) add(s string) Position { - p.Byte += len(s) - if n := strings.Count(s, "\n"); n > 0 { - p.Line += n - s = s[strings.LastIndex(s, "\n")+1:] - p.LineRune = 1 - } - p.LineRune += utf8.RuneCountInString(s) - return p -} - -// An Expr represents an input element. -type Expr interface { - // Span returns the start and end position of the expression, - // excluding leading or trailing comments. - Span() (start, end Position) - - // Comment returns the comments attached to the expression. - // This method would normally be named 'Comments' but that - // would interfere with embedding a type of the same name. - Comment() *Comments -} - -// A Comment represents a single // comment. -type Comment struct { - Start Position - Token string // without trailing newline - Suffix bool // an end of line (not whole line) comment -} - -// Comments collects the comments associated with an expression. -type Comments struct { - Before []Comment // whole-line comments before this expression - Suffix []Comment // end-of-line comments after this expression - - // For top-level expressions only, After lists whole-line - // comments following the expression. - After []Comment -} - -// Comment returns the receiver. This isn't useful by itself, but -// a Comments struct is embedded into all the expression -// implementation types, and this gives each of those a Comment -// method to satisfy the Expr interface. -func (c *Comments) Comment() *Comments { - return c -} - -// A FileSyntax represents an entire go.mod file. -type FileSyntax struct { - Name string // file path - Comments - Stmt []Expr -} - -func (x *FileSyntax) Span() (start, end Position) { - if len(x.Stmt) == 0 { - return - } - start, _ = x.Stmt[0].Span() - _, end = x.Stmt[len(x.Stmt)-1].Span() - return start, end -} - -func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line { - if hint == nil { - // If no hint given, add to the last statement of the given type. - Loop: - for i := len(x.Stmt) - 1; i >= 0; i-- { - stmt := x.Stmt[i] - switch stmt := stmt.(type) { - case *Line: - if stmt.Token != nil && stmt.Token[0] == tokens[0] { - hint = stmt - break Loop - } - case *LineBlock: - if stmt.Token[0] == tokens[0] { - hint = stmt - break Loop - } - } - } - } - - if hint != nil { - for i, stmt := range x.Stmt { - switch stmt := stmt.(type) { - case *Line: - if stmt == hint { - // Convert line to line block. - stmt.InBlock = true - block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}} - stmt.Token = stmt.Token[1:] - x.Stmt[i] = block - new := &Line{Token: tokens[1:], InBlock: true} - block.Line = append(block.Line, new) - return new - } - case *LineBlock: - if stmt == hint { - new := &Line{Token: tokens[1:], InBlock: true} - stmt.Line = append(stmt.Line, new) - return new - } - for j, line := range stmt.Line { - if line == hint { - // Add new line after hint. - stmt.Line = append(stmt.Line, nil) - copy(stmt.Line[j+2:], stmt.Line[j+1:]) - new := &Line{Token: tokens[1:], InBlock: true} - stmt.Line[j+1] = new - return new - } - } - } - } - } - - new := &Line{Token: tokens} - x.Stmt = append(x.Stmt, new) - return new -} - -func (x *FileSyntax) updateLine(line *Line, tokens ...string) { - if line.InBlock { - tokens = tokens[1:] - } - line.Token = tokens -} - -func (x *FileSyntax) removeLine(line *Line) { - line.Token = nil -} - -// Cleanup cleans up the file syntax x after any edit operations. -// To avoid quadratic behavior, removeLine marks the line as dead -// by setting line.Token = nil but does not remove it from the slice -// in which it appears. After edits have all been indicated, -// calling Cleanup cleans out the dead lines. -func (x *FileSyntax) Cleanup() { - w := 0 - for _, stmt := range x.Stmt { - switch stmt := stmt.(type) { - case *Line: - if stmt.Token == nil { - continue - } - case *LineBlock: - ww := 0 - for _, line := range stmt.Line { - if line.Token != nil { - stmt.Line[ww] = line - ww++ - } - } - if ww == 0 { - continue - } - if ww == 1 { - // Collapse block into single line. - line := &Line{ - Comments: Comments{ - Before: commentsAdd(stmt.Before, stmt.Line[0].Before), - Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix), - After: commentsAdd(stmt.Line[0].After, stmt.After), - }, - Token: stringsAdd(stmt.Token, stmt.Line[0].Token), - } - x.Stmt[w] = line - w++ - continue - } - stmt.Line = stmt.Line[:ww] - } - x.Stmt[w] = stmt - w++ - } - x.Stmt = x.Stmt[:w] -} - -func commentsAdd(x, y []Comment) []Comment { - return append(x[:len(x):len(x)], y...) -} - -func stringsAdd(x, y []string) []string { - return append(x[:len(x):len(x)], y...) -} - -// A CommentBlock represents a top-level block of comments separate -// from any rule. -type CommentBlock struct { - Comments - Start Position -} - -func (x *CommentBlock) Span() (start, end Position) { - return x.Start, x.Start -} - -// A Line is a single line of tokens. -type Line struct { - Comments - Start Position - Token []string - InBlock bool - End Position -} - -func (x *Line) Span() (start, end Position) { - return x.Start, x.End -} - -// A LineBlock is a factored block of lines, like -// -// require ( -// "x" -// "y" -// ) -// -type LineBlock struct { - Comments - Start Position - LParen LParen - Token []string - Line []*Line - RParen RParen -} - -func (x *LineBlock) Span() (start, end Position) { - return x.Start, x.RParen.Pos.add(")") -} - -// An LParen represents the beginning of a parenthesized line block. -// It is a place to store suffix comments. -type LParen struct { - Comments - Pos Position -} - -func (x *LParen) Span() (start, end Position) { - return x.Pos, x.Pos.add(")") -} - -// An RParen represents the end of a parenthesized line block. -// It is a place to store whole-line (before) comments. -type RParen struct { - Comments - Pos Position -} - -func (x *RParen) Span() (start, end Position) { - return x.Pos, x.Pos.add(")") -} - -// An input represents a single input file being parsed. -type input struct { - // Lexing state. - filename string // name of input file, for errors - complete []byte // entire input - remaining []byte // remaining input - token []byte // token being scanned - lastToken string // most recently returned token, for error messages - pos Position // current input position - comments []Comment // accumulated comments - endRule int // position of end of current rule - - // Parser state. - file *FileSyntax // returned top-level syntax tree - parseError error // error encountered during parsing - - // Comment assignment state. - pre []Expr // all expressions, in preorder traversal - post []Expr // all expressions, in postorder traversal -} - -func newInput(filename string, data []byte) *input { - return &input{ - filename: filename, - complete: data, - remaining: data, - pos: Position{Line: 1, LineRune: 1, Byte: 0}, - } -} - -// parse parses the input file. -func parse(file string, data []byte) (f *FileSyntax, err error) { - in := newInput(file, data) - // The parser panics for both routine errors like syntax errors - // and for programmer bugs like array index errors. - // Turn both into error returns. Catching bug panics is - // especially important when processing many files. - defer func() { - if e := recover(); e != nil { - if e == in.parseError { - err = in.parseError - } else { - err = fmt.Errorf("%s:%d:%d: internal error: %v", in.filename, in.pos.Line, in.pos.LineRune, e) - } - } - }() - - // Invoke the parser. - in.parseFile() - if in.parseError != nil { - return nil, in.parseError - } - in.file.Name = in.filename - - // Assign comments to nearby syntax. - in.assignComments() - - return in.file, nil -} - -// Error is called to report an error. -// The reason s is often "syntax error". -// Error does not return: it panics. -func (in *input) Error(s string) { - if s == "syntax error" && in.lastToken != "" { - s += " near " + in.lastToken - } - in.parseError = fmt.Errorf("%s:%d:%d: %v", in.filename, in.pos.Line, in.pos.LineRune, s) - panic(in.parseError) -} - -// eof reports whether the input has reached end of file. -func (in *input) eof() bool { - return len(in.remaining) == 0 -} - -// peekRune returns the next rune in the input without consuming it. -func (in *input) peekRune() int { - if len(in.remaining) == 0 { - return 0 - } - r, _ := utf8.DecodeRune(in.remaining) - return int(r) -} - -// peekPrefix reports whether the remaining input begins with the given prefix. -func (in *input) peekPrefix(prefix string) bool { - // This is like bytes.HasPrefix(in.remaining, []byte(prefix)) - // but without the allocation of the []byte copy of prefix. - for i := 0; i < len(prefix); i++ { - if i >= len(in.remaining) || in.remaining[i] != prefix[i] { - return false - } - } - return true -} - -// readRune consumes and returns the next rune in the input. -func (in *input) readRune() int { - if len(in.remaining) == 0 { - in.Error("internal lexer error: readRune at EOF") - } - r, size := utf8.DecodeRune(in.remaining) - in.remaining = in.remaining[size:] - if r == '\n' { - in.pos.Line++ - in.pos.LineRune = 1 - } else { - in.pos.LineRune++ - } - in.pos.Byte += size - return int(r) -} - -type symType struct { - pos Position - endPos Position - text string -} - -// startToken marks the beginning of the next input token. -// It must be followed by a call to endToken, once the token has -// been consumed using readRune. -func (in *input) startToken(sym *symType) { - in.token = in.remaining - sym.text = "" - sym.pos = in.pos -} - -// endToken marks the end of an input token. -// It records the actual token string in sym.text if the caller -// has not done that already. -func (in *input) endToken(sym *symType) { - if sym.text == "" { - tok := string(in.token[:len(in.token)-len(in.remaining)]) - sym.text = tok - in.lastToken = sym.text - } - sym.endPos = in.pos -} - -// lex is called from the parser to obtain the next input token. -// It returns the token value (either a rune like '+' or a symbolic token _FOR) -// and sets val to the data associated with the token. -// For all our input tokens, the associated data is -// val.Pos (the position where the token begins) -// and val.Token (the input string corresponding to the token). -func (in *input) lex(sym *symType) int { - // Skip past spaces, stopping at non-space or EOF. - countNL := 0 // number of newlines we've skipped past - for !in.eof() { - // Skip over spaces. Count newlines so we can give the parser - // information about where top-level blank lines are, - // for top-level comment assignment. - c := in.peekRune() - if c == ' ' || c == '\t' || c == '\r' { - in.readRune() - continue - } - - // Comment runs to end of line. - if in.peekPrefix("//") { - in.startToken(sym) - - // Is this comment the only thing on its line? - // Find the last \n before this // and see if it's all - // spaces from there to here. - i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n")) - suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0 - in.readRune() - in.readRune() - - // Consume comment. - for len(in.remaining) > 0 && in.readRune() != '\n' { - } - in.endToken(sym) - - sym.text = strings.TrimRight(sym.text, "\n") - in.lastToken = "comment" - - // If we are at top level (not in a statement), hand the comment to - // the parser as a _COMMENT token. The grammar is written - // to handle top-level comments itself. - if !suffix { - // Not in a statement. Tell parser about top-level comment. - return _COMMENT - } - - // Otherwise, save comment for later attachment to syntax tree. - if countNL > 1 { - in.comments = append(in.comments, Comment{sym.pos, "", false}) - } - in.comments = append(in.comments, Comment{sym.pos, sym.text, suffix}) - countNL = 1 - return _EOL - } - - if in.peekPrefix("/*") { - in.Error(fmt.Sprintf("mod files must use // comments (not /* */ comments)")) - } - - // Found non-space non-comment. - break - } - - // Found the beginning of the next token. - in.startToken(sym) - defer in.endToken(sym) - - // End of file. - if in.eof() { - in.lastToken = "EOF" - return _EOF - } - - // Punctuation tokens. - switch c := in.peekRune(); c { - case '\n': - in.readRune() - return c - - case '(': - in.readRune() - return c - - case ')': - in.readRune() - return c - - case '"', '`': // quoted string - quote := c - in.readRune() - for { - if in.eof() { - in.pos = sym.pos - in.Error("unexpected EOF in string") - } - if in.peekRune() == '\n' { - in.Error("unexpected newline in string") - } - c := in.readRune() - if c == quote { - break - } - if c == '\\' && quote != '`' { - if in.eof() { - in.pos = sym.pos - in.Error("unexpected EOF in string") - } - in.readRune() - } - } - in.endToken(sym) - return _STRING - } - - // Checked all punctuation. Must be identifier token. - if c := in.peekRune(); !isIdent(c) { - in.Error(fmt.Sprintf("unexpected input character %#q", c)) - } - - // Scan over identifier. - for isIdent(in.peekRune()) { - if in.peekPrefix("//") { - break - } - if in.peekPrefix("/*") { - in.Error(fmt.Sprintf("mod files must use // comments (not /* */ comments)")) - } - in.readRune() - } - return _IDENT -} - -// isIdent reports whether c is an identifier rune. -// We treat nearly all runes as identifier runes. -func isIdent(c int) bool { - return c != 0 && !unicode.IsSpace(rune(c)) -} - -// Comment assignment. -// We build two lists of all subexpressions, preorder and postorder. -// The preorder list is ordered by start location, with outer expressions first. -// The postorder list is ordered by end location, with outer expressions last. -// We use the preorder list to assign each whole-line comment to the syntax -// immediately following it, and we use the postorder list to assign each -// end-of-line comment to the syntax immediately preceding it. - -// order walks the expression adding it and its subexpressions to the -// preorder and postorder lists. -func (in *input) order(x Expr) { - if x != nil { - in.pre = append(in.pre, x) - } - switch x := x.(type) { - default: - panic(fmt.Errorf("order: unexpected type %T", x)) - case nil: - // nothing - case *LParen, *RParen: - // nothing - case *CommentBlock: - // nothing - case *Line: - // nothing - case *FileSyntax: - for _, stmt := range x.Stmt { - in.order(stmt) - } - case *LineBlock: - in.order(&x.LParen) - for _, l := range x.Line { - in.order(l) - } - in.order(&x.RParen) - } - if x != nil { - in.post = append(in.post, x) - } -} - -// assignComments attaches comments to nearby syntax. -func (in *input) assignComments() { - const debug = false - - // Generate preorder and postorder lists. - in.order(in.file) - - // Split into whole-line comments and suffix comments. - var line, suffix []Comment - for _, com := range in.comments { - if com.Suffix { - suffix = append(suffix, com) - } else { - line = append(line, com) - } - } - - if debug { - for _, c := range line { - fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) - } - } - - // Assign line comments to syntax immediately following. - for _, x := range in.pre { - start, _ := x.Span() - if debug { - fmt.Printf("pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte) - } - xcom := x.Comment() - for len(line) > 0 && start.Byte >= line[0].Start.Byte { - if debug { - fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte) - } - xcom.Before = append(xcom.Before, line[0]) - line = line[1:] - } - } - - // Remaining line comments go at end of file. - in.file.After = append(in.file.After, line...) - - if debug { - for _, c := range suffix { - fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) - } - } - - // Assign suffix comments to syntax immediately before. - for i := len(in.post) - 1; i >= 0; i-- { - x := in.post[i] - - start, end := x.Span() - if debug { - fmt.Printf("post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte) - } - - // Do not assign suffix comments to end of line block or whole file. - // Instead assign them to the last element inside. - switch x.(type) { - case *FileSyntax: - continue - } - - // Do not assign suffix comments to something that starts - // on an earlier line, so that in - // - // x ( y - // z ) // comment - // - // we assign the comment to z and not to x ( ... ). - if start.Line != end.Line { - continue - } - xcom := x.Comment() - for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte { - if debug { - fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte) - } - xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1]) - suffix = suffix[:len(suffix)-1] - } - } - - // We assigned suffix comments in reverse. - // If multiple suffix comments were appended to the same - // expression node, they are now in reverse. Fix that. - for _, x := range in.post { - reverseComments(x.Comment().Suffix) - } - - // Remaining suffix comments go at beginning of file. - in.file.Before = append(in.file.Before, suffix...) -} - -// reverseComments reverses the []Comment list. -func reverseComments(list []Comment) { - for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { - list[i], list[j] = list[j], list[i] - } -} - -func (in *input) parseFile() { - in.file = new(FileSyntax) - var sym symType - var cb *CommentBlock - for { - tok := in.lex(&sym) - switch tok { - case '\n': - if cb != nil { - in.file.Stmt = append(in.file.Stmt, cb) - cb = nil - } - case _COMMENT: - if cb == nil { - cb = &CommentBlock{Start: sym.pos} - } - com := cb.Comment() - com.Before = append(com.Before, Comment{Start: sym.pos, Token: sym.text}) - case _EOF: - if cb != nil { - in.file.Stmt = append(in.file.Stmt, cb) - } - return - default: - in.parseStmt(&sym) - if cb != nil { - in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before - cb = nil - } - } - } -} - -func (in *input) parseStmt(sym *symType) { - start := sym.pos - end := sym.endPos - token := []string{sym.text} - for { - tok := in.lex(sym) - switch tok { - case '\n', _EOF, _EOL: - in.file.Stmt = append(in.file.Stmt, &Line{ - Start: start, - Token: token, - End: end, - }) - return - case '(': - in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, token, sym)) - return - default: - token = append(token, sym.text) - end = sym.endPos - } - } -} - -func (in *input) parseLineBlock(start Position, token []string, sym *symType) *LineBlock { - x := &LineBlock{ - Start: start, - Token: token, - LParen: LParen{Pos: sym.pos}, - } - var comments []Comment - for { - tok := in.lex(sym) - switch tok { - case _EOL: - // ignore - case '\n': - if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" { - comments = append(comments, Comment{}) - } - case _COMMENT: - comments = append(comments, Comment{Start: sym.pos, Token: sym.text}) - case _EOF: - in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune)) - case ')': - x.RParen.Before = comments - x.RParen.Pos = sym.pos - tok = in.lex(sym) - if tok != '\n' && tok != _EOF && tok != _EOL { - in.Error("syntax error (expected newline after closing paren)") - } - return x - default: - l := in.parseLine(sym) - x.Line = append(x.Line, l) - l.Comment().Before = comments - comments = nil - } - } -} - -func (in *input) parseLine(sym *symType) *Line { - start := sym.pos - end := sym.endPos - token := []string{sym.text} - for { - tok := in.lex(sym) - switch tok { - case '\n', _EOF, _EOL: - return &Line{ - Start: start, - Token: token, - End: end, - InBlock: true, - } - default: - token = append(token, sym.text) - end = sym.endPos - } - } -} - -const ( - _EOF = -(1 + iota) - _EOL - _IDENT - _STRING - _COMMENT -) - -var ( - slashSlash = []byte("//") - moduleStr = []byte("module") -) - -// ModulePath returns the module path from the gomod file text. -// If it cannot find a module path, it returns an empty string. -// It is tolerant of unrelated problems in the go.mod file. -func ModulePath(mod []byte) string { - for len(mod) > 0 { - line := mod - mod = nil - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, mod = line[:i], line[i+1:] - } - if i := bytes.Index(line, slashSlash); i >= 0 { - line = line[:i] - } - line = bytes.TrimSpace(line) - if !bytes.HasPrefix(line, moduleStr) { - continue - } - line = line[len(moduleStr):] - n := len(line) - line = bytes.TrimSpace(line) - if len(line) == n || len(line) == 0 { - continue - } - - if line[0] == '"' || line[0] == '`' { - p, err := strconv.Unquote(string(line)) - if err != nil { - return "" // malformed quoted string or multiline module path - } - return p - } - - return string(line) - } - return "" // missing module path -} diff --git a/modfile/read_test.go b/modfile/read_test.go deleted file mode 100644 index 15e9fada..00000000 --- a/modfile/read_test.go +++ /dev/null @@ -1,334 +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 modfile - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/rogpeppe/go-internal/internal/textutil" -) - -// exists reports whether the named file exists. -func exists(name string) bool { - _, err := os.Stat(name) - return err == nil -} - -// Test that reading and then writing the golden files -// does not change their output. -func TestPrintGolden(t *testing.T) { - outs, err := filepath.Glob("testdata/*.golden") - if err != nil { - t.Fatal(err) - } - for _, out := range outs { - testPrint(t, out, out) - } -} - -// testPrint is a helper for testing the printer. -// It reads the file named in, reformats it, and compares -// the result to the file named out. -func testPrint(t *testing.T, in, out string) { - data, err := ioutil.ReadFile(in) - if err != nil { - t.Error(err) - return - } - - golden, err := ioutil.ReadFile(out) - if err != nil { - t.Error(err) - return - } - - base := "testdata/" + filepath.Base(in) - f, err := parse(in, data) - if err != nil { - t.Error(err) - return - } - - ndata := Format(f) - - if !bytes.Equal(ndata, golden) { - t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) - tdiff(t, string(golden), string(ndata)) - return - } -} - -func TestParseLax(t *testing.T) { - badFile := []byte(`module m - surprise attack - x y ( - z - ) - exclude v1.2.3 - replace <-!!! - `) - _, err := ParseLax("file", badFile, nil) - if err != nil { - t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err) - } -} - -// Test that when files in the testdata directory are parsed -// and printed and parsed again, we get the same parse tree -// both times. -func TestPrintParse(t *testing.T) { - outs, err := filepath.Glob("testdata/*") - if err != nil { - t.Fatal(err) - } - for _, out := range outs { - data, err := ioutil.ReadFile(out) - if err != nil { - t.Error(err) - continue - } - - base := "testdata/" + filepath.Base(out) - f, err := parse(base, data) - if err != nil { - t.Errorf("parsing original: %v", err) - continue - } - - ndata := Format(f) - f2, err := parse(base, ndata) - if err != nil { - t.Errorf("parsing reformatted: %v", err) - continue - } - - eq := eqchecker{file: base} - if err := eq.check(f, f2); err != nil { - t.Errorf("not equal (parse/Format/parse): %v", err) - } - - pf1, err := Parse(base, data, nil) - if err != nil { - switch base { - case "testdata/replace2.in", "testdata/gopkg.in.golden": - t.Errorf("should parse %v: %v", base, err) - } - } - if err == nil { - pf2, err := Parse(base, ndata, nil) - if err != nil { - t.Errorf("Parsing reformatted: %v", err) - continue - } - eq := eqchecker{file: base} - if err := eq.check(pf1, pf2); err != nil { - t.Errorf("not equal (parse/Format/Parse): %v", err) - } - - ndata2, err := pf1.Format() - if err != nil { - t.Errorf("reformat: %v", err) - } - pf3, err := Parse(base, ndata2, nil) - if err != nil { - t.Errorf("Parsing reformatted2: %v", err) - continue - } - eq = eqchecker{file: base} - if err := eq.check(pf1, pf3); err != nil { - t.Errorf("not equal (Parse/Format/Parse): %v", err) - } - ndata = ndata2 - } - - if strings.HasSuffix(out, ".in") { - golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden") - if err != nil { - t.Error(err) - continue - } - if !bytes.Equal(ndata, golden) { - t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) - tdiff(t, string(golden), string(ndata)) - return - } - } - } -} - -// An eqchecker holds state for checking the equality of two parse trees. -type eqchecker struct { - file string - pos Position -} - -// errorf returns an error described by the printf-style format and arguments, -// inserting the current file position before the error text. -func (eq *eqchecker) errorf(format string, args ...interface{}) error { - return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line, - fmt.Sprintf(format, args...)) -} - -// check checks that v and w represent the same parse tree. -// If not, it returns an error describing the first difference. -func (eq *eqchecker) check(v, w interface{}) error { - return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w)) -} - -var ( - posType = reflect.TypeOf(Position{}) - commentsType = reflect.TypeOf(Comments{}) -) - -// checkValue checks that v and w represent the same parse tree. -// If not, it returns an error describing the first difference. -func (eq *eqchecker) checkValue(v, w reflect.Value) error { - // inner returns the innermost expression for v. - // if v is a non-nil interface value, it returns the concrete - // value in the interface. - inner := func(v reflect.Value) reflect.Value { - for { - if v.Kind() == reflect.Interface && !v.IsNil() { - v = v.Elem() - continue - } - break - } - return v - } - - v = inner(v) - w = inner(w) - if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid { - return nil - } - if v.Kind() == reflect.Invalid { - return eq.errorf("nil interface became %s", w.Type()) - } - if w.Kind() == reflect.Invalid { - return eq.errorf("%s became nil interface", v.Type()) - } - - if v.Type() != w.Type() { - return eq.errorf("%s became %s", v.Type(), w.Type()) - } - - if p, ok := v.Interface().(Expr); ok { - eq.pos, _ = p.Span() - } - - switch v.Kind() { - default: - return eq.errorf("unexpected type %s", v.Type()) - - case reflect.Bool, reflect.Int, reflect.String: - vi := v.Interface() - wi := w.Interface() - if vi != wi { - return eq.errorf("%v became %v", vi, wi) - } - - case reflect.Slice: - vl := v.Len() - wl := w.Len() - for i := 0; i < vl || i < wl; i++ { - if i >= vl { - return eq.errorf("unexpected %s", w.Index(i).Type()) - } - if i >= wl { - return eq.errorf("missing %s", v.Index(i).Type()) - } - if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil { - return err - } - } - - case reflect.Struct: - // Fields in struct must match. - t := v.Type() - n := t.NumField() - for i := 0; i < n; i++ { - tf := t.Field(i) - switch { - default: - if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil { - return err - } - - case tf.Type == posType: // ignore positions - case tf.Type == commentsType: // ignore comment assignment - } - } - - case reflect.Ptr, reflect.Interface: - if v.IsNil() != w.IsNil() { - if v.IsNil() { - return eq.errorf("unexpected %s", w.Elem().Type()) - } - return eq.errorf("missing %s", v.Elem().Type()) - } - if err := eq.checkValue(v.Elem(), w.Elem()); err != nil { - return err - } - } - return nil -} - -// tdiff logs the diff output to t.Error. -func tdiff(t *testing.T, a, b string) { - data := textutil.Diff(a, b) - t.Error(string(data)) -} - -var modulePathTests = []struct { - input []byte - expected string -}{ - {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, - {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, - {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, - {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, - {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"}, - {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"}, - {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"}, - {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"}, - {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"}, - {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""}, - {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"}, - {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, - {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, - {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, - {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, - {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, - {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, - {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"}, - {input: []byte("module \" \""), expected: " "}, - {input: []byte("module "), expected: ""}, - {input: []byte("module \" a/b/c \""), expected: " a/b/c "}, - {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"}, -} - -func TestModulePath(t *testing.T) { - for _, test := range modulePathTests { - t.Run(string(test.input), func(t *testing.T) { - result := ModulePath(test.input) - if result != test.expected { - t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected) - } - }) - } -} diff --git a/modfile/replace_test.go b/modfile/replace_test.go deleted file mode 100644 index d0ca0dd5..00000000 --- a/modfile/replace_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package modfile - -import ( - "bytes" - "fmt" - "testing" -) - -var addDropReplaceTests = []struct { - in string - oldpath string - oldvers string - newpath string - newvers string - dropOld bool - out string -}{ - { - ` - module m - require x.y/z v1.2.3 - `, - "x.y/z", "v1.2.3", - "my.x.y/z", "v1.2.3", - false, - ` - module m - require x.y/z v1.2.3 - replace x.y/z v1.2.3 => my.x.y/z v1.2.3 - `, - }, - - { - ` - module m - require x.y/z v1.2.3 - replace x.y/z => my.x.y/z v0.0.0-20190214113530-db6c41c15648 - `, - "x.y/z", "", - "my.x.y/z", "v1.2.3", - true, - ` - module m - require x.y/z v1.2.3 - replace x.y/z => my.x.y/z v1.2.3 - `, - }, - { - ` - module m - require x.y/z v1.2.3 - replace x.y/z => my.x.y/z v0.0.0-20190214113530-db6c41c15648 - `, - "x.y/z", "", - "", "", // empty newpath and newvers - drop only, no add - true, - ` - module m - require x.y/z v1.2.3 - `, - }, -} - -func TestAddDropReplace(t *testing.T) { - for i, tt := range addDropReplaceTests { - t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { - f, err := Parse("in", []byte(tt.in), nil) - if err != nil { - t.Fatal(err) - } - g, err := Parse("out", []byte(tt.out), nil) - if err != nil { - t.Fatal(err) - } - golden, err := g.Format() - if err != nil { - t.Fatal(err) - } - if tt.dropOld { - if err := f.DropReplace(tt.oldpath, tt.oldvers); err != nil { - t.Fatal(err) - } - } - if tt.newpath != "" || tt.newvers != "" { - if err := f.AddReplace(tt.oldpath, tt.oldvers, tt.newpath, tt.newvers); err != nil { - t.Fatal(err) - } - } - f.Cleanup() - out, err := f.Format() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(out, golden) { - t.Errorf("have:\n%s\nwant:\n%s", out, golden) - } - }) - } -} diff --git a/modfile/rule.go b/modfile/rule.go deleted file mode 100644 index 24d275f1..00000000 --- a/modfile/rule.go +++ /dev/null @@ -1,724 +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 modfile - -import ( - "bytes" - "errors" - "fmt" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "unicode" - - "github.com/rogpeppe/go-internal/module" - "github.com/rogpeppe/go-internal/semver" -) - -// A File is the parsed, interpreted form of a go.mod file. -type File struct { - Module *Module - Go *Go - Require []*Require - Exclude []*Exclude - Replace []*Replace - - Syntax *FileSyntax -} - -// A Module is the module statement. -type Module struct { - Mod module.Version - Syntax *Line -} - -// A Go is the go statement. -type Go struct { - Version string // "1.23" - Syntax *Line -} - -// A Require is a single require statement. -type Require struct { - Mod module.Version - Indirect bool // has "// indirect" comment - Syntax *Line -} - -// An Exclude is a single exclude statement. -type Exclude struct { - Mod module.Version - Syntax *Line -} - -// A Replace is a single replace statement. -type Replace struct { - Old module.Version - New module.Version - Syntax *Line -} - -func (f *File) AddModuleStmt(path string) error { - if f.Syntax == nil { - f.Syntax = new(FileSyntax) - } - if f.Module == nil { - f.Module = &Module{ - Mod: module.Version{Path: path}, - Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), - } - } else { - f.Module.Mod.Path = path - f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) - } - return nil -} - -func (f *File) AddComment(text string) { - if f.Syntax == nil { - f.Syntax = new(FileSyntax) - } - f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ - Comments: Comments{ - Before: []Comment{ - { - Token: text, - }, - }, - }, - }) -} - -type VersionFixer func(path, version string) (string, error) - -// Parse parses the data, reported in errors as being from file, -// into a File struct. It applies fix, if non-nil, to canonicalize all module versions found. -func Parse(file string, data []byte, fix VersionFixer) (*File, error) { - return parseToFile(file, data, fix, true) -} - -// ParseLax is like Parse but ignores unknown statements. -// It is used when parsing go.mod files other than the main module, -// under the theory that most statement types we add in the future will -// only apply in the main module, like exclude and replace, -// and so we get better gradual deployments if old go commands -// simply ignore those statements when found in go.mod files -// in dependencies. -func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { - return parseToFile(file, data, fix, false) -} - -func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) { - fs, err := parse(file, data) - if err != nil { - return nil, err - } - f := &File{ - Syntax: fs, - } - - var errs bytes.Buffer - for _, x := range fs.Stmt { - switch x := x.(type) { - case *Line: - f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict) - - case *LineBlock: - if len(x.Token) > 1 { - if strict { - fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) - } - continue - } - switch x.Token[0] { - default: - if strict { - fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) - } - continue - case "module", "require", "exclude", "replace": - for _, l := range x.Line { - f.add(&errs, l, x.Token[0], l.Token, fix, strict) - } - } - } - } - - if errs.Len() > 0 { - return nil, errors.New(strings.TrimRight(errs.String(), "\n")) - } - return f, nil -} - -var goVersionRE = regexp.MustCompile(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`) - -func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) { - // If strict is false, this module is a dependency. - // We ignore all unknown directives as well as main-module-only - // directives like replace and exclude. It will work better for - // forward compatibility if we can depend on modules that have unknown - // statements (presumed relevant only when acting as the main module) - // and simply ignore those statements. - if !strict { - switch verb { - case "module", "require", "go": - // want these even for dependency go.mods - default: - return - } - } - - switch verb { - default: - fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb) - - case "go": - if f.Go != nil { - fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line) - return - } - if len(args) != 1 || !goVersionRE.MatchString(args[0]) { - fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line) - return - } - f.Go = &Go{Syntax: line} - f.Go.Version = args[0] - case "module": - if f.Module != nil { - fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line) - return - } - f.Module = &Module{Syntax: line} - if len(args) != 1 { - - fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line) - return - } - s, err := parseString(&args[0]) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - f.Module.Mod = module.Version{Path: s} - case "require", "exclude": - if len(args) != 2 { - fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb) - return - } - s, err := parseString(&args[0]) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - old := args[1] - v, err := parseVersion(s, &args[1], fix) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid module version %q: %v\n", f.Syntax.Name, line.Start.Line, old, err) - return - } - pathMajor, err := modulePathMajor(s) - if err != nil { - fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - if !module.MatchPathMajor(v, pathMajor) { - if pathMajor == "" { - pathMajor = "v0 or v1" - } - fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v) - return - } - if verb == "require" { - f.Require = append(f.Require, &Require{ - Mod: module.Version{Path: s, Version: v}, - Syntax: line, - Indirect: isIndirect(line), - }) - } else { - f.Exclude = append(f.Exclude, &Exclude{ - Mod: module.Version{Path: s, Version: v}, - Syntax: line, - }) - } - case "replace": - arrow := 2 - if len(args) >= 2 && args[1] == "=>" { - arrow = 1 - } - if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { - fmt.Fprintf(errs, "%s:%d: usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory\n", f.Syntax.Name, line.Start.Line, verb, verb) - return - } - s, err := parseString(&args[0]) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - pathMajor, err := modulePathMajor(s) - if err != nil { - fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - var v string - if arrow == 2 { - old := args[1] - v, err = parseVersion(s, &args[1], fix) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) - return - } - if !module.MatchPathMajor(v, pathMajor) { - if pathMajor == "" { - pathMajor = "v0 or v1" - } - fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, pathMajor, semver.Major(v), v) - return - } - } - ns, err := parseString(&args[arrow+1]) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) - return - } - nv := "" - if len(args) == arrow+2 { - if !IsDirectoryPath(ns) { - fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)\n", f.Syntax.Name, line.Start.Line) - return - } - if filepath.Separator == '/' && strings.Contains(ns, `\`) { - fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)\n", f.Syntax.Name, line.Start.Line) - return - } - } - if len(args) == arrow+3 { - old := args[arrow+1] - nv, err = parseVersion(ns, &args[arrow+2], fix) - if err != nil { - fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) - return - } - if IsDirectoryPath(ns) { - fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version\n", f.Syntax.Name, line.Start.Line, ns) - return - } - } - f.Replace = append(f.Replace, &Replace{ - Old: module.Version{Path: s, Version: v}, - New: module.Version{Path: ns, Version: nv}, - Syntax: line, - }) - } -} - -// isIndirect reports whether line has a "// indirect" comment, -// meaning it is in go.mod only for its effect on indirect dependencies, -// so that it can be dropped entirely once the effective version of the -// indirect dependency reaches the given minimum version. -func isIndirect(line *Line) bool { - if len(line.Suffix) == 0 { - return false - } - f := strings.Fields(line.Suffix[0].Token) - return (len(f) == 2 && f[1] == "indirect" || len(f) > 2 && f[1] == "indirect;") && f[0] == "//" -} - -// setIndirect sets line to have (or not have) a "// indirect" comment. -func setIndirect(line *Line, indirect bool) { - if isIndirect(line) == indirect { - return - } - if indirect { - // Adding comment. - if len(line.Suffix) == 0 { - // New comment. - line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} - return - } - // Insert at beginning of existing comment. - com := &line.Suffix[0] - space := " " - if len(com.Token) > 2 && com.Token[2] == ' ' || com.Token[2] == '\t' { - space = "" - } - com.Token = "// indirect;" + space + com.Token[2:] - return - } - - // Removing comment. - f := strings.Fields(line.Suffix[0].Token) - if len(f) == 2 { - // Remove whole comment. - line.Suffix = nil - return - } - - // Remove comment prefix. - com := &line.Suffix[0] - i := strings.Index(com.Token, "indirect;") - com.Token = "//" + com.Token[i+len("indirect;"):] -} - -// IsDirectoryPath reports whether the given path should be interpreted -// as a directory path. Just like on the go command line, relative paths -// and rooted paths are directory paths; the rest are module paths. -func IsDirectoryPath(ns string) bool { - // Because go.mod files can move from one system to another, - // we check all known path syntaxes, both Unix and Windows. - return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") || - strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) || - len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' -} - -// MustQuote reports whether s must be quoted in order to appear as -// a single token in a go.mod line. -func MustQuote(s string) bool { - for _, r := range s { - if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' { - return true - } - } - return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") -} - -// AutoQuote returns s or, if quoting is required for s to appear in a go.mod, -// the quotation of s. -func AutoQuote(s string) string { - if MustQuote(s) { - return strconv.Quote(s) - } - return s -} - -func parseString(s *string) (string, error) { - t := *s - if strings.HasPrefix(t, `"`) { - var err error - if t, err = strconv.Unquote(t); err != nil { - return "", err - } - } else if strings.ContainsAny(t, "\"'`") { - // Other quotes are reserved both for possible future expansion - // and to avoid confusion. For example if someone types 'x' - // we want that to be a syntax error and not a literal x in literal quotation marks. - return "", fmt.Errorf("unquoted string cannot contain quote") - } - *s = AutoQuote(t) - return t, nil -} - -func parseVersion(path string, s *string, fix VersionFixer) (string, error) { - t, err := parseString(s) - if err != nil { - return "", err - } - if fix != nil { - var err error - t, err = fix(path, t) - if err != nil { - return "", err - } - } - if v := module.CanonicalVersion(t); v != "" { - *s = v - return *s, nil - } - return "", fmt.Errorf("version must be of the form v1.2.3") -} - -func modulePathMajor(path string) (string, error) { - _, major, ok := module.SplitPathVersion(path) - if !ok { - return "", fmt.Errorf("invalid module path") - } - return major, nil -} - -func (f *File) Format() ([]byte, error) { - return Format(f.Syntax), nil -} - -// Cleanup cleans up the file f after any edit operations. -// To avoid quadratic behavior, modifications like DropRequire -// clear the entry but do not remove it from the slice. -// Cleanup cleans out all the cleared entries. -func (f *File) Cleanup() { - w := 0 - for _, r := range f.Require { - if r.Mod.Path != "" { - f.Require[w] = r - w++ - } - } - f.Require = f.Require[:w] - - w = 0 - for _, x := range f.Exclude { - if x.Mod.Path != "" { - f.Exclude[w] = x - w++ - } - } - f.Exclude = f.Exclude[:w] - - w = 0 - for _, r := range f.Replace { - if r.Old.Path != "" { - f.Replace[w] = r - w++ - } - } - f.Replace = f.Replace[:w] - - f.Syntax.Cleanup() -} - -func (f *File) AddRequire(path, vers string) error { - need := true - for _, r := range f.Require { - if r.Mod.Path == path { - if need { - r.Mod.Version = vers - f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) - need = false - } else { - f.Syntax.removeLine(r.Syntax) - *r = Require{} - } - } - } - - if need { - f.AddNewRequire(path, vers, false) - } - return nil -} - -func (f *File) AddNewRequire(path, vers string, indirect bool) { - line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) - setIndirect(line, indirect) - f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line}) -} - -func (f *File) SetRequire(req []*Require) { - need := make(map[string]string) - indirect := make(map[string]bool) - for _, r := range req { - need[r.Mod.Path] = r.Mod.Version - indirect[r.Mod.Path] = r.Indirect - } - - for _, r := range f.Require { - if v, ok := need[r.Mod.Path]; ok { - r.Mod.Version = v - r.Indirect = indirect[r.Mod.Path] - } - } - - var newStmts []Expr - for _, stmt := range f.Syntax.Stmt { - switch stmt := stmt.(type) { - case *LineBlock: - if len(stmt.Token) > 0 && stmt.Token[0] == "require" { - var newLines []*Line - for _, line := range stmt.Line { - if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" { - line.Token[1] = need[p] - delete(need, p) - setIndirect(line, indirect[p]) - newLines = append(newLines, line) - } - } - if len(newLines) == 0 { - continue // drop stmt - } - stmt.Line = newLines - } - - case *Line: - if len(stmt.Token) > 0 && stmt.Token[0] == "require" { - if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" { - stmt.Token[2] = need[p] - delete(need, p) - setIndirect(stmt, indirect[p]) - } else { - continue // drop stmt - } - } - } - newStmts = append(newStmts, stmt) - } - f.Syntax.Stmt = newStmts - - for path, vers := range need { - f.AddNewRequire(path, vers, indirect[path]) - } - f.SortBlocks() -} - -func (f *File) DropRequire(path string) error { - for _, r := range f.Require { - if r.Mod.Path == path { - f.Syntax.removeLine(r.Syntax) - *r = Require{} - } - } - return nil -} - -func (f *File) AddExclude(path, vers string) error { - var hint *Line - for _, x := range f.Exclude { - if x.Mod.Path == path && x.Mod.Version == vers { - return nil - } - if x.Mod.Path == path { - hint = x.Syntax - } - } - - f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) - return nil -} - -func (f *File) DropExclude(path, vers string) error { - for _, x := range f.Exclude { - if x.Mod.Path == path && x.Mod.Version == vers { - f.Syntax.removeLine(x.Syntax) - *x = Exclude{} - } - } - return nil -} - -func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { - need := true - old := module.Version{Path: oldPath, Version: oldVers} - new := module.Version{Path: newPath, Version: newVers} - tokens := []string{"replace", AutoQuote(oldPath)} - if oldVers != "" { - tokens = append(tokens, oldVers) - } - tokens = append(tokens, "=>", AutoQuote(newPath)) - if newVers != "" { - tokens = append(tokens, newVers) - } - - var hint *Line - for _, r := range f.Replace { - if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { - if need { - // Found replacement for old; update to use new. - r.New = new - f.Syntax.updateLine(r.Syntax, tokens...) - need = false - continue - } - // Already added; delete other replacements for same. - f.Syntax.removeLine(r.Syntax) - *r = Replace{} - } - if r.Old.Path == oldPath { - hint = r.Syntax - } - } - if need { - f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)}) - } - return nil -} - -func (f *File) DropReplace(oldPath, oldVers string) error { - for _, r := range f.Replace { - if r.Old.Path == oldPath && r.Old.Version == oldVers { - f.Syntax.removeLine(r.Syntax) - *r = Replace{} - } - } - return nil -} - -func (f *File) SortBlocks() { - f.removeDups() // otherwise sorting is unsafe - - for _, stmt := range f.Syntax.Stmt { - block, ok := stmt.(*LineBlock) - if !ok { - continue - } - sort.Slice(block.Line, func(i, j int) bool { - li := block.Line[i] - lj := block.Line[j] - for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { - if li.Token[k] != lj.Token[k] { - return li.Token[k] < lj.Token[k] - } - } - return len(li.Token) < len(lj.Token) - }) - } -} - -func (f *File) removeDups() { - have := make(map[module.Version]bool) - kill := make(map[*Line]bool) - for _, x := range f.Exclude { - if have[x.Mod] { - kill[x.Syntax] = true - continue - } - have[x.Mod] = true - } - var excl []*Exclude - for _, x := range f.Exclude { - if !kill[x.Syntax] { - excl = append(excl, x) - } - } - f.Exclude = excl - - have = make(map[module.Version]bool) - // Later replacements take priority over earlier ones. - for i := len(f.Replace) - 1; i >= 0; i-- { - x := f.Replace[i] - if have[x.Old] { - kill[x.Syntax] = true - continue - } - have[x.Old] = true - } - var repl []*Replace - for _, x := range f.Replace { - if !kill[x.Syntax] { - repl = append(repl, x) - } - } - f.Replace = repl - - var stmts []Expr - for _, stmt := range f.Syntax.Stmt { - switch stmt := stmt.(type) { - case *Line: - if kill[stmt] { - continue - } - case *LineBlock: - var lines []*Line - for _, line := range stmt.Line { - if !kill[line] { - lines = append(lines, line) - } - } - stmt.Line = lines - if len(lines) == 0 { - continue - } - } - stmts = append(stmts, stmt) - } - f.Syntax.Stmt = stmts -} diff --git a/modfile/rule_test.go b/modfile/rule_test.go deleted file mode 100644 index b88ad629..00000000 --- a/modfile/rule_test.go +++ /dev/null @@ -1,90 +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 modfile - -import ( - "bytes" - "fmt" - "testing" -) - -var addRequireTests = []struct { - in string - path string - vers string - out string -}{ - { - ` - module m - require x.y/z v1.2.3 - `, - "x.y/z", "v1.5.6", - ` - module m - require x.y/z v1.5.6 - `, - }, - { - ` - module m - require x.y/z v1.2.3 - `, - "x.y/w", "v1.5.6", - ` - module m - require ( - x.y/z v1.2.3 - x.y/w v1.5.6 - ) - `, - }, - { - ` - module m - require x.y/z v1.2.3 - require x.y/q/v2 v2.3.4 - `, - "x.y/w", "v1.5.6", - ` - module m - require x.y/z v1.2.3 - require ( - x.y/q/v2 v2.3.4 - x.y/w v1.5.6 - ) - `, - }, -} - -func TestAddRequire(t *testing.T) { - for i, tt := range addRequireTests { - t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { - f, err := Parse("in", []byte(tt.in), nil) - if err != nil { - t.Fatal(err) - } - g, err := Parse("out", []byte(tt.out), nil) - if err != nil { - t.Fatal(err) - } - golden, err := g.Format() - if err != nil { - t.Fatal(err) - } - - if err := f.AddRequire(tt.path, tt.vers); err != nil { - t.Fatal(err) - } - out, err := f.Format() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(out, golden) { - t.Errorf("have:\n%s\nwant:\n%s", out, golden) - } - }) - } -} diff --git a/modfile/testdata/block.golden b/modfile/testdata/block.golden deleted file mode 100644 index 4aa2d634..00000000 --- a/modfile/testdata/block.golden +++ /dev/null @@ -1,29 +0,0 @@ -// comment -x "y" z - -// block -block ( // block-eol - // x-before-line - - "x" ( y // x-eol - "x1" - "x2" - // line - "x3" - "x4" - - "x5" - - // y-line - "y" // y-eol - - "z" // z-eol -) // block-eol2 - -block2 ( - x - y - z -) - -// eof diff --git a/modfile/testdata/block.in b/modfile/testdata/block.in deleted file mode 100644 index 1dfae65f..00000000 --- a/modfile/testdata/block.in +++ /dev/null @@ -1,29 +0,0 @@ -// comment -x "y" z - -// block -block ( // block-eol - // x-before-line - - "x" ( y // x-eol - "x1" - "x2" - // line - "x3" - "x4" - - "x5" - - // y-line - "y" // y-eol - - "z" // z-eol -) // block-eol2 - - -block2 (x - y - z -) - -// eof diff --git a/modfile/testdata/comment.golden b/modfile/testdata/comment.golden deleted file mode 100644 index 75f3b844..00000000 --- a/modfile/testdata/comment.golden +++ /dev/null @@ -1,10 +0,0 @@ -// comment -module "x" // eol - -// mid comment - -// comment 2 -// comment 2 line 2 -module "y" // eoy - -// comment 3 diff --git a/modfile/testdata/comment.in b/modfile/testdata/comment.in deleted file mode 100644 index bfc2492b..00000000 --- a/modfile/testdata/comment.in +++ /dev/null @@ -1,8 +0,0 @@ -// comment -module "x" // eol -// mid comment - -// comment 2 -// comment 2 line 2 -module "y" // eoy -// comment 3 diff --git a/modfile/testdata/empty.golden b/modfile/testdata/empty.golden deleted file mode 100644 index e69de29b..00000000 diff --git a/modfile/testdata/empty.in b/modfile/testdata/empty.in deleted file mode 100644 index e69de29b..00000000 diff --git a/modfile/testdata/gopkg.in.golden b/modfile/testdata/gopkg.in.golden deleted file mode 100644 index 41669b3a..00000000 --- a/modfile/testdata/gopkg.in.golden +++ /dev/null @@ -1,6 +0,0 @@ -module x - -require ( - gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 - gopkg.in/yaml.v2 v2.2.1 -) diff --git a/modfile/testdata/module.golden b/modfile/testdata/module.golden deleted file mode 100644 index 78ba9439..00000000 --- a/modfile/testdata/module.golden +++ /dev/null @@ -1 +0,0 @@ -module abc diff --git a/modfile/testdata/module.in b/modfile/testdata/module.in deleted file mode 100644 index 08f38362..00000000 --- a/modfile/testdata/module.in +++ /dev/null @@ -1 +0,0 @@ -module "abc" diff --git a/modfile/testdata/replace.golden b/modfile/testdata/replace.golden deleted file mode 100644 index 5d6abcfc..00000000 --- a/modfile/testdata/replace.golden +++ /dev/null @@ -1,5 +0,0 @@ -module abc - -replace xyz v1.2.3 => /tmp/z - -replace xyz v1.3.4 => my/xyz v1.3.4-me diff --git a/modfile/testdata/replace.in b/modfile/testdata/replace.in deleted file mode 100644 index 68524994..00000000 --- a/modfile/testdata/replace.in +++ /dev/null @@ -1,5 +0,0 @@ -module "abc" - -replace "xyz" v1.2.3 => "/tmp/z" - -replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me diff --git a/modfile/testdata/replace2.golden b/modfile/testdata/replace2.golden deleted file mode 100644 index e1d9c728..00000000 --- a/modfile/testdata/replace2.golden +++ /dev/null @@ -1,10 +0,0 @@ -module abc - -replace ( - xyz v1.2.3 => /tmp/z - xyz v1.3.4 => my/xyz v1.3.4-me - xyz v1.4.5 => "/tmp/my dir" - xyz v1.5.6 => my/xyz v1.5.6 - - xyz => my/other/xyz v1.5.4 -) diff --git a/modfile/testdata/replace2.in b/modfile/testdata/replace2.in deleted file mode 100644 index 78646986..00000000 --- a/modfile/testdata/replace2.in +++ /dev/null @@ -1,10 +0,0 @@ -module "abc" - -replace ( - "xyz" v1.2.3 => "/tmp/z" - "xyz" v1.3.4 => "my/xyz" "v1.3.4-me" - xyz "v1.4.5" => "/tmp/my dir" - xyz v1.5.6 => my/xyz v1.5.6 - - xyz => my/other/xyz v1.5.4 -) diff --git a/modfile/testdata/rule1.golden b/modfile/testdata/rule1.golden deleted file mode 100644 index 8a5c7258..00000000 --- a/modfile/testdata/rule1.golden +++ /dev/null @@ -1,7 +0,0 @@ -module "x" - -module "y" - -require "x" - -require x diff --git a/module/forward.go b/module/forward.go new file mode 100644 index 00000000..e1cca274 --- /dev/null +++ b/module/forward.go @@ -0,0 +1,61 @@ +// Package module is a thin forwarding layer on top of +// [golang.org/x/mod/module]. Note that the Encode* and +// Decode* functions map to Escape* and Unescape* +// in that package. +// +// See that package for documentation on everything else. +// +// Deprecated: use [golang.org/x/mod/module] instead. +package module + +import "golang.org/x/mod/module" + +type Version = module.Version + +func Check(path, version string) error { + return module.Check(path, version) +} + +func CheckPath(path string) error { + return module.CheckPath(path) +} + +func CheckImportPath(path string) error { + return module.CheckImportPath(path) +} + +func CheckFilePath(path string) error { + return module.CheckFilePath(path) +} + +func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { + return module.SplitPathVersion(path) +} + +func MatchPathMajor(v, pathMajor string) bool { + return module.MatchPathMajor(v, pathMajor) +} + +func CanonicalVersion(v string) string { + return module.CanonicalVersion(v) +} + +func Sort(list []Version) { + module.Sort(list) +} + +func EncodePath(path string) (encoding string, err error) { + return module.EscapePath(path) +} + +func EncodeVersion(v string) (encoding string, err error) { + return module.EscapeVersion(v) +} + +func DecodePath(encoding string) (path string, err error) { + return module.UnescapePath(encoding) +} + +func DecodeVersion(encoding string) (v string, err error) { + return module.UnescapeVersion(encoding) +} diff --git a/module/module.go b/module/module.go deleted file mode 100644 index 3ff6d9bf..00000000 --- a/module/module.go +++ /dev/null @@ -1,540 +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 module defines the module.Version type -// along with support code. -package module - -// IMPORTANT NOTE -// -// This file essentially defines the set of valid import paths for the go command. -// There are many subtle considerations, including Unicode ambiguity, -// security, network, and file system representations. -// -// This file also defines the set of valid module path and version combinations, -// another topic with many subtle considerations. -// -// Changes to the semantics in this file require approval from rsc. - -import ( - "fmt" - "sort" - "strings" - "unicode" - "unicode/utf8" - - "github.com/rogpeppe/go-internal/semver" -) - -// A Version is defined by a module path and version pair. -type Version struct { - Path string - - // Version is usually a semantic version in canonical form. - // There are two exceptions to this general rule. - // First, the top-level target of a build has no specific version - // and uses Version = "". - // Second, during MVS calculations the version "none" is used - // to represent the decision to take no version of a given module. - Version string `json:",omitempty"` -} - -// Check checks that a given module path, version pair is valid. -// In addition to the path being a valid module path -// and the version being a valid semantic version, -// the two must correspond. -// For example, the path "yaml/v2" only corresponds to -// semantic versions beginning with "v2.". -func Check(path, version string) error { - if err := CheckPath(path); err != nil { - return err - } - if !semver.IsValid(version) { - return fmt.Errorf("malformed semantic version %v", version) - } - _, pathMajor, _ := SplitPathVersion(path) - if !MatchPathMajor(version, pathMajor) { - if pathMajor == "" { - pathMajor = "v0 or v1" - } - if pathMajor[0] == '.' { // .v1 - pathMajor = pathMajor[1:] - } - return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor) - } - return nil -} - -// firstPathOK reports whether r can appear in the first element of a module path. -// The first element of the path must be an LDH domain name, at least for now. -// To avoid case ambiguity, the domain name must be entirely lower case. -func firstPathOK(r rune) bool { - return r == '-' || r == '.' || - '0' <= r && r <= '9' || - 'a' <= r && r <= 'z' -} - -// pathOK reports whether r can appear in an import path element. -// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~. -// This matches what "go get" has historically recognized in import paths. -// TODO(rsc): We would like to allow Unicode letters, but that requires additional -// care in the safe encoding (see note below). -func pathOK(r rune) bool { - if r < utf8.RuneSelf { - return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' || - '0' <= r && r <= '9' || - 'A' <= r && r <= 'Z' || - 'a' <= r && r <= 'z' - } - return false -} - -// fileNameOK reports whether r can appear in a file name. -// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. -// If we expand the set of allowed characters here, we have to -// work harder at detecting potential case-folding and normalization collisions. -// See note about "safe encoding" below. -func fileNameOK(r rune) bool { - if r < utf8.RuneSelf { - // Entire set of ASCII punctuation, from which we remove characters: - // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ - // We disallow some shell special characters: " ' * < > ? ` | - // (Note that some of those are disallowed by the Windows file system as well.) - // We also disallow path separators / : and \ (fileNameOK is only called on path element characters). - // We allow spaces (U+0020) in file names. - const allowed = "!#$%&()+,-.=@[]^_{}~ " - if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' { - return true - } - for i := 0; i < len(allowed); i++ { - if rune(allowed[i]) == r { - return true - } - } - return false - } - // It may be OK to add more ASCII punctuation here, but only carefully. - // For example Windows disallows < > \, and macOS disallows :, so we must not allow those. - return unicode.IsLetter(r) -} - -// CheckPath checks that a module path is valid. -func CheckPath(path string) error { - if err := checkPath(path, false); err != nil { - return fmt.Errorf("malformed module path %q: %v", path, err) - } - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - if i == 0 { - return fmt.Errorf("malformed module path %q: leading slash", path) - } - if !strings.Contains(path[:i], ".") { - return fmt.Errorf("malformed module path %q: missing dot in first path element", path) - } - if path[0] == '-' { - return fmt.Errorf("malformed module path %q: leading dash in first path element", path) - } - for _, r := range path[:i] { - if !firstPathOK(r) { - return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r) - } - } - if _, _, ok := SplitPathVersion(path); !ok { - return fmt.Errorf("malformed module path %q: invalid version", path) - } - return nil -} - -// CheckImportPath checks that an import path is valid. -func CheckImportPath(path string) error { - if err := checkPath(path, false); err != nil { - return fmt.Errorf("malformed import path %q: %v", path, err) - } - return nil -} - -// checkPath checks that a general path is valid. -// It returns an error describing why but not mentioning path. -// Because these checks apply to both module paths and import paths, -// the caller is expected to add the "malformed ___ path %q: " prefix. -// fileName indicates whether the final element of the path is a file name -// (as opposed to a directory name). -func checkPath(path string, fileName bool) error { - if !utf8.ValidString(path) { - return fmt.Errorf("invalid UTF-8") - } - if path == "" { - return fmt.Errorf("empty string") - } - if strings.Contains(path, "..") { - return fmt.Errorf("double dot") - } - if strings.Contains(path, "//") { - return fmt.Errorf("double slash") - } - if path[len(path)-1] == '/' { - return fmt.Errorf("trailing slash") - } - elemStart := 0 - for i, r := range path { - if r == '/' { - if err := checkElem(path[elemStart:i], fileName); err != nil { - return err - } - elemStart = i + 1 - } - } - if err := checkElem(path[elemStart:], fileName); err != nil { - return err - } - return nil -} - -// checkElem checks whether an individual path element is valid. -// fileName indicates whether the element is a file name (not a directory name). -func checkElem(elem string, fileName bool) error { - if elem == "" { - return fmt.Errorf("empty path element") - } - if strings.Count(elem, ".") == len(elem) { - return fmt.Errorf("invalid path element %q", elem) - } - if elem[0] == '.' && !fileName { - return fmt.Errorf("leading dot in path element") - } - if elem[len(elem)-1] == '.' { - return fmt.Errorf("trailing dot in path element") - } - charOK := pathOK - if fileName { - charOK = fileNameOK - } - for _, r := range elem { - if !charOK(r) { - return fmt.Errorf("invalid char %q", r) - } - } - - // Windows disallows a bunch of path elements, sadly. - // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file - short := elem - if i := strings.Index(short, "."); i >= 0 { - short = short[:i] - } - for _, bad := range badWindowsNames { - if strings.EqualFold(bad, short) { - return fmt.Errorf("disallowed path element %q", elem) - } - } - return nil -} - -// CheckFilePath checks whether a slash-separated file path is valid. -func CheckFilePath(path string) error { - if err := checkPath(path, true); err != nil { - return fmt.Errorf("malformed file path %q: %v", path, err) - } - return nil -} - -// badWindowsNames are the reserved file path elements on Windows. -// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file -var badWindowsNames = []string{ - "CON", - "PRN", - "AUX", - "NUL", - "COM1", - "COM2", - "COM3", - "COM4", - "COM5", - "COM6", - "COM7", - "COM8", - "COM9", - "LPT1", - "LPT2", - "LPT3", - "LPT4", - "LPT5", - "LPT6", - "LPT7", - "LPT8", - "LPT9", -} - -// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path -// and version is either empty or "/vN" for N >= 2. -// As a special case, gopkg.in paths are recognized directly; -// they require ".vN" instead of "/vN", and for all N, not just N >= 2. -func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { - if strings.HasPrefix(path, "gopkg.in/") { - return splitGopkgIn(path) - } - - i := len(path) - dot := false - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') { - if path[i-1] == '.' { - dot = true - } - i-- - } - if i <= 1 || path[i-1] != 'v' || path[i-2] != '/' { - return path, "", true - } - prefix, pathMajor = path[:i-2], path[i-2:] - if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" { - return path, "", false - } - return prefix, pathMajor, true -} - -// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths. -func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { - if !strings.HasPrefix(path, "gopkg.in/") { - return path, "", false - } - i := len(path) - if strings.HasSuffix(path, "-unstable") { - i -= len("-unstable") - } - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') { - i-- - } - if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' { - // All gopkg.in paths must end in vN for some N. - return path, "", false - } - prefix, pathMajor = path[:i-2], path[i-2:] - if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" { - return path, "", false - } - return prefix, pathMajor, true -} - -// MatchPathMajor reports whether the semantic version v -// matches the path major version pathMajor. -func MatchPathMajor(v, pathMajor string) bool { - if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { - pathMajor = strings.TrimSuffix(pathMajor, "-unstable") - } - if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { - // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. - // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. - return true - } - m := semver.Major(v) - if pathMajor == "" { - return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" - } - return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:] -} - -// CanonicalVersion returns the canonical form of the version string v. -// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible". -func CanonicalVersion(v string) string { - cv := semver.Canonical(v) - if semver.Build(v) == "+incompatible" { - cv += "+incompatible" - } - return cv -} - -// Sort sorts the list by Path, breaking ties by comparing Versions. -func Sort(list []Version) { - sort.Slice(list, func(i, j int) bool { - mi := list[i] - mj := list[j] - if mi.Path != mj.Path { - return mi.Path < mj.Path - } - // To help go.sum formatting, allow version/file. - // Compare semver prefix by semver rules, - // file by string order. - vi := mi.Version - vj := mj.Version - var fi, fj string - if k := strings.Index(vi, "/"); k >= 0 { - vi, fi = vi[:k], vi[k:] - } - if k := strings.Index(vj, "/"); k >= 0 { - vj, fj = vj[:k], vj[k:] - } - if vi != vj { - return semver.Compare(vi, vj) < 0 - } - return fi < fj - }) -} - -// Safe encodings -// -// Module paths appear as substrings of file system paths -// (in the download cache) and of web server URLs in the proxy protocol. -// In general we cannot rely on file systems to be case-sensitive, -// nor can we rely on web servers, since they read from file systems. -// That is, we cannot rely on the file system to keep rsc.io/QUOTE -// and rsc.io/quote separate. Windows and macOS don't. -// Instead, we must never require two different casings of a file path. -// Because we want the download cache to match the proxy protocol, -// and because we want the proxy protocol to be possible to serve -// from a tree of static files (which might be stored on a case-insensitive -// file system), the proxy protocol must never require two different casings -// of a URL path either. -// -// One possibility would be to make the safe encoding be the lowercase -// hexadecimal encoding of the actual path bytes. This would avoid ever -// needing different casings of a file path, but it would be fairly illegible -// to most programmers when those paths appeared in the file system -// (including in file paths in compiler errors and stack traces) -// in web server logs, and so on. Instead, we want a safe encoding that -// leaves most paths unaltered. -// -// The safe encoding is this: -// replace every uppercase letter with an exclamation mark -// followed by the letter's lowercase equivalent. -// -// For example, -// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. -// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy -// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. -// -// Import paths that avoid upper-case letters are left unchanged. -// Note that because import paths are ASCII-only and avoid various -// problematic punctuation (like : < and >), the safe encoding is also ASCII-only -// and avoids the same problematic punctuation. -// -// Import paths have never allowed exclamation marks, so there is no -// need to define how to encode a literal !. -// -// Although paths are disallowed from using Unicode (see pathOK above), -// the eventual plan is to allow Unicode letters as well, to assume that -// file systems and URLs are Unicode-safe (storing UTF-8), and apply -// the !-for-uppercase convention. Note however that not all runes that -// are different but case-fold equivalent are an upper/lower pair. -// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) -// are considered to case-fold to each other. When we do add Unicode -// letters, we must not assume that upper/lower are the only case-equivalent pairs. -// Perhaps the Kelvin symbol would be disallowed entirely, for example. -// Or perhaps it would encode as "!!k", or perhaps as "(212A)". -// -// Also, it would be nice to allow Unicode marks as well as letters, -// but marks include combining marks, and then we must deal not -// only with case folding but also normalization: both U+00E9 ('é') -// and U+0065 U+0301 ('e' followed by combining acute accent) -// look the same on the page and are treated by some file systems -// as the same path. If we do allow Unicode marks in paths, there -// must be some kind of normalization to allow only one canonical -// encoding of any character used in an import path. - -// EncodePath returns the safe encoding of the given module path. -// It fails if the module path is invalid. -func EncodePath(path string) (encoding string, err error) { - if err := CheckPath(path); err != nil { - return "", err - } - - return encodeString(path) -} - -// EncodeVersion returns the safe encoding of the given module version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func EncodeVersion(v string) (encoding string, err error) { - if err := checkElem(v, true); err != nil || strings.Contains(v, "!") { - return "", fmt.Errorf("disallowed version string %q", v) - } - return encodeString(v) -} - -func encodeString(s string) (encoding string, err error) { - haveUpper := false - for _, r := range s { - if r == '!' || r >= utf8.RuneSelf { - // This should be disallowed by CheckPath, but diagnose anyway. - // The correctness of the encoding loop below depends on it. - return "", fmt.Errorf("internal error: inconsistency in EncodePath") - } - if 'A' <= r && r <= 'Z' { - haveUpper = true - } - } - - if !haveUpper { - return s, nil - } - - var buf []byte - for _, r := range s { - if 'A' <= r && r <= 'Z' { - buf = append(buf, '!', byte(r+'a'-'A')) - } else { - buf = append(buf, byte(r)) - } - } - return string(buf), nil -} - -// DecodePath returns the module path of the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid path. -func DecodePath(encoding string) (path string, err error) { - path, ok := decodeString(encoding) - if !ok { - return "", fmt.Errorf("invalid module path encoding %q", encoding) - } - if err := CheckPath(path); err != nil { - return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err) - } - return path, nil -} - -// DecodeVersion returns the version string for the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func DecodeVersion(encoding string) (v string, err error) { - v, ok := decodeString(encoding) - if !ok { - return "", fmt.Errorf("invalid version encoding %q", encoding) - } - if err := checkElem(v, true); err != nil { - return "", fmt.Errorf("disallowed version string %q", v) - } - return v, nil -} - -func decodeString(encoding string) (string, bool) { - var buf []byte - - bang := false - for _, r := range encoding { - if r >= utf8.RuneSelf { - return "", false - } - if bang { - bang = false - if r < 'a' || 'z' < r { - return "", false - } - buf = append(buf, byte(r+'A'-'a')) - continue - } - if r == '!' { - bang = true - continue - } - if 'A' <= r && r <= 'Z' { - return "", false - } - buf = append(buf, byte(r)) - } - if bang { - return "", false - } - return string(buf), true -} diff --git a/module/module_test.go b/module/module_test.go deleted file mode 100644 index f21d620d..00000000 --- a/module/module_test.go +++ /dev/null @@ -1,318 +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 module - -import "testing" - -var checkTests = []struct { - path string - version string - ok bool -}{ - {"rsc.io/quote", "0.1.0", false}, - {"rsc io/quote", "v1.0.0", false}, - - {"github.com/go-yaml/yaml", "v0.8.0", true}, - {"github.com/go-yaml/yaml", "v1.0.0", true}, - {"github.com/go-yaml/yaml", "v2.0.0", false}, - {"github.com/go-yaml/yaml", "v2.1.5", false}, - {"github.com/go-yaml/yaml", "v3.0.0", false}, - - {"github.com/go-yaml/yaml/v2", "v1.0.0", false}, - {"github.com/go-yaml/yaml/v2", "v2.0.0", true}, - {"github.com/go-yaml/yaml/v2", "v2.1.5", true}, - {"github.com/go-yaml/yaml/v2", "v3.0.0", false}, - - {"gopkg.in/yaml.v0", "v0.8.0", true}, - {"gopkg.in/yaml.v0", "v1.0.0", false}, - {"gopkg.in/yaml.v0", "v2.0.0", false}, - {"gopkg.in/yaml.v0", "v2.1.5", false}, - {"gopkg.in/yaml.v0", "v3.0.0", false}, - - {"gopkg.in/yaml.v1", "v0.8.0", false}, - {"gopkg.in/yaml.v1", "v1.0.0", true}, - {"gopkg.in/yaml.v1", "v2.0.0", false}, - {"gopkg.in/yaml.v1", "v2.1.5", false}, - {"gopkg.in/yaml.v1", "v3.0.0", false}, - - // For gopkg.in, .v1 means v1 only (not v0). - // But early versions of vgo still generated v0 pseudo-versions for it. - // Even though now we'd generate those as v1 pseudo-versions, - // we accept the old pseudo-versions to avoid breaking existing go.mod files. - // For example gopkg.in/yaml.v2@v2.2.1's go.mod requires check.v1 at a v0 pseudo-version. - {"gopkg.in/check.v1", "v0.0.0", false}, - {"gopkg.in/check.v1", "v0.0.0-20160102150405-abcdef123456", true}, - - {"gopkg.in/yaml.v2", "v1.0.0", false}, - {"gopkg.in/yaml.v2", "v2.0.0", true}, - {"gopkg.in/yaml.v2", "v2.1.5", true}, - {"gopkg.in/yaml.v2", "v3.0.0", false}, - - {"rsc.io/quote", "v17.0.0", false}, - {"rsc.io/quote", "v17.0.0+incompatible", true}, -} - -func TestCheck(t *testing.T) { - for _, tt := range checkTests { - err := Check(tt.path, tt.version) - if tt.ok && err != nil { - t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err) - } else if !tt.ok && err == nil { - t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version) - } - } -} - -var checkPathTests = []struct { - path string - ok bool - importOK bool - fileOK bool -}{ - {"x.y/z", true, true, true}, - {"x.y", true, true, true}, - - {"", false, false, false}, - {"x.y/\xFFz", false, false, false}, - {"/x.y/z", false, false, false}, - {"x./z", false, false, false}, - {".x/z", false, false, true}, - {"-x/z", false, true, true}, - {"x..y/z", false, false, false}, - {"x.y/z/../../w", false, false, false}, - {"x.y//z", false, false, false}, - {"x.y/z//w", false, false, false}, - {"x.y/z/", false, false, false}, - - {"x.y/z/v0", false, true, true}, - {"x.y/z/v1", false, true, true}, - {"x.y/z/v2", true, true, true}, - {"x.y/z/v2.0", false, true, true}, - {"X.y/z", false, true, true}, - - {"!x.y/z", false, false, true}, - {"_x.y/z", false, true, true}, - {"x.y!/z", false, false, true}, - {"x.y\"/z", false, false, false}, - {"x.y#/z", false, false, true}, - {"x.y$/z", false, false, true}, - {"x.y%/z", false, false, true}, - {"x.y&/z", false, false, true}, - {"x.y'/z", false, false, false}, - {"x.y(/z", false, false, true}, - {"x.y)/z", false, false, true}, - {"x.y*/z", false, false, false}, - {"x.y+/z", false, true, true}, - {"x.y,/z", false, false, true}, - {"x.y-/z", true, true, true}, - {"x.y./zt", false, false, false}, - {"x.y:/z", false, false, false}, - {"x.y;/z", false, false, false}, - {"x.y/z", false, false, false}, - {"x.y?/z", false, false, false}, - {"x.y@/z", false, false, true}, - {"x.y[/z", false, false, true}, - {"x.y\\/z", false, false, false}, - {"x.y]/z", false, false, true}, - {"x.y^/z", false, false, true}, - {"x.y_/z", false, true, true}, - {"x.y`/z", false, false, false}, - {"x.y{/z", false, false, true}, - {"x.y}/z", false, false, true}, - {"x.y~/z", false, true, true}, - {"x.y/z!", false, false, true}, - {"x.y/z\"", false, false, false}, - {"x.y/z#", false, false, true}, - {"x.y/z$", false, false, true}, - {"x.y/z%", false, false, true}, - {"x.y/z&", false, false, true}, - {"x.y/z'", false, false, false}, - {"x.y/z(", false, false, true}, - {"x.y/z)", false, false, true}, - {"x.y/z*", false, false, false}, - {"x.y/z+", true, true, true}, - {"x.y/z,", false, false, true}, - {"x.y/z-", true, true, true}, - {"x.y/z.t", true, true, true}, - {"x.y/z/t", true, true, true}, - {"x.y/z:", false, false, false}, - {"x.y/z;", false, false, false}, - {"x.y/z<", false, false, false}, - {"x.y/z=", false, false, true}, - {"x.y/z>", false, false, false}, - {"x.y/z?", false, false, false}, - {"x.y/z@", false, false, true}, - {"x.y/z[", false, false, true}, - {"x.y/z\\", false, false, false}, - {"x.y/z]", false, false, true}, - {"x.y/z^", false, false, true}, - {"x.y/z_", true, true, true}, - {"x.y/z`", false, false, false}, - {"x.y/z{", false, false, true}, - {"x.y/z}", false, false, true}, - {"x.y/z~", true, true, true}, - {"x.y/x.foo", true, true, true}, - {"x.y/aux.foo", false, false, false}, - {"x.y/prn", false, false, false}, - {"x.y/prn2", true, true, true}, - {"x.y/com", true, true, true}, - {"x.y/com1", false, false, false}, - {"x.y/com1.txt", false, false, false}, - {"x.y/calm1", true, true, true}, - {"github.com/!123/logrus", false, false, true}, - - // TODO: CL 41822 allowed Unicode letters in old "go get" - // without due consideration of the implications, and only on github.com (!). - // For now, we disallow non-ASCII characters in module mode, - // in both module paths and general import paths, - // until we can get the implications right. - // When we do, we'll enable them everywhere, not just for GitHub. - {"github.com/user/unicode/испытание", false, false, true}, - - {"../x", false, false, false}, - {"./y", false, false, false}, - {"x:y", false, false, false}, - {`\temp\foo`, false, false, false}, - {".gitignore", false, false, true}, - {".github/ISSUE_TEMPLATE", false, false, true}, - {"x☺y", false, false, false}, -} - -func TestCheckPath(t *testing.T) { - for _, tt := range checkPathTests { - err := CheckPath(tt.path) - if tt.ok && err != nil { - t.Errorf("CheckPath(%q) = %v, wanted nil error", tt.path, err) - } else if !tt.ok && err == nil { - t.Errorf("CheckPath(%q) succeeded, wanted error", tt.path) - } - - err = CheckImportPath(tt.path) - if tt.importOK && err != nil { - t.Errorf("CheckImportPath(%q) = %v, wanted nil error", tt.path, err) - } else if !tt.importOK && err == nil { - t.Errorf("CheckImportPath(%q) succeeded, wanted error", tt.path) - } - - err = CheckFilePath(tt.path) - if tt.fileOK && err != nil { - t.Errorf("CheckFilePath(%q) = %v, wanted nil error", tt.path, err) - } else if !tt.fileOK && err == nil { - t.Errorf("CheckFilePath(%q) succeeded, wanted error", tt.path) - } - } -} - -var splitPathVersionTests = []struct { - pathPrefix string - version string -}{ - {"x.y/z", ""}, - {"x.y/z", "/v2"}, - {"x.y/z", "/v3"}, - {"gopkg.in/yaml", ".v0"}, - {"gopkg.in/yaml", ".v1"}, - {"gopkg.in/yaml", ".v2"}, - {"gopkg.in/yaml", ".v3"}, -} - -func TestSplitPathVersion(t *testing.T) { - for _, tt := range splitPathVersionTests { - pathPrefix, version, ok := SplitPathVersion(tt.pathPrefix + tt.version) - if pathPrefix != tt.pathPrefix || version != tt.version || !ok { - t.Errorf("SplitPathVersion(%q) = %q, %q, %v, want %q, %q, true", tt.pathPrefix+tt.version, pathPrefix, version, ok, tt.pathPrefix, tt.version) - } - } - - for _, tt := range checkPathTests { - pathPrefix, version, ok := SplitPathVersion(tt.path) - if pathPrefix+version != tt.path { - t.Errorf("SplitPathVersion(%q) = %q, %q, %v, doesn't add to input", tt.path, pathPrefix, version, ok) - } - } -} - -var encodeTests = []struct { - path string - enc string // empty means same as path -}{ - {path: "ascii.com/abcdefghijklmnopqrstuvwxyz.-+/~_0123456789"}, - {path: "github.com/GoogleCloudPlatform/omega", enc: "github.com/!google!cloud!platform/omega"}, -} - -func TestEncodePath(t *testing.T) { - // Check invalid paths. - for _, tt := range checkPathTests { - if !tt.ok { - _, err := EncodePath(tt.path) - if err == nil { - t.Errorf("EncodePath(%q): succeeded, want error (invalid path)", tt.path) - } - } - } - - // Check encodings. - for _, tt := range encodeTests { - enc, err := EncodePath(tt.path) - if err != nil { - t.Errorf("EncodePath(%q): unexpected error: %v", tt.path, err) - continue - } - want := tt.enc - if want == "" { - want = tt.path - } - if enc != want { - t.Errorf("EncodePath(%q) = %q, want %q", tt.path, enc, want) - } - } -} - -var badDecode = []string{ - "github.com/GoogleCloudPlatform/omega", - "github.com/!google!cloud!platform!/omega", - "github.com/!0google!cloud!platform/omega", - "github.com/!_google!cloud!platform/omega", - "github.com/!!google!cloud!platform/omega", - "", -} - -func TestDecodePath(t *testing.T) { - // Check invalid decodings. - for _, bad := range badDecode { - _, err := DecodePath(bad) - if err == nil { - t.Errorf("DecodePath(%q): succeeded, want error (invalid decoding)", bad) - } - } - - // Check invalid paths (or maybe decodings). - for _, tt := range checkPathTests { - if !tt.ok { - path, err := DecodePath(tt.path) - if err == nil { - t.Errorf("DecodePath(%q) = %q, want error (invalid path)", tt.path, path) - } - } - } - - // Check encodings. - for _, tt := range encodeTests { - enc := tt.enc - if enc == "" { - enc = tt.path - } - path, err := DecodePath(enc) - if err != nil { - t.Errorf("DecodePath(%q): unexpected error: %v", enc, err) - continue - } - if path != tt.path { - t.Errorf("DecodePath(%q) = %q, want %q", enc, path, tt.path) - } - } -} diff --git a/renameio/renameio.go b/renameio/renameio.go index 8f59e1a5..234ec555 100644 --- a/renameio/renameio.go +++ b/renameio/renameio.go @@ -3,6 +3,8 @@ // license that can be found in the LICENSE file. // Package renameio writes files atomically by renaming temporary files. +// +// Deprecated: Use [github.com/google/renameio/v2] instead. package renameio import ( diff --git a/robustio/robustio.go b/robustio/robustio.go new file mode 100644 index 00000000..15b33773 --- /dev/null +++ b/robustio/robustio.go @@ -0,0 +1,53 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package robustio wraps I/O functions that are prone to failure on Windows, +// transparently retrying errors up to an arbitrary timeout. +// +// Errors are classified heuristically and retries are bounded, so the functions +// in this package do not completely eliminate spurious errors. However, they do +// significantly reduce the rate of failure in practice. +// +// If so, the error will likely wrap one of: +// The functions in this package do not completely eliminate spurious errors, +// but substantially reduce their rate of occurrence in practice. +package robustio + +// Rename is like os.Rename, but on Windows retries errors that may occur if the +// file is concurrently read or overwritten. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func Rename(oldpath, newpath string) error { + return rename(oldpath, newpath) +} + +// ReadFile is like os.ReadFile, but on Windows retries errors that may +// occur if the file is concurrently replaced. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func ReadFile(filename string) ([]byte, error) { + return readFile(filename) +} + +// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur +// if an executable file in the directory has recently been executed. +// +// (See golang.org/issue/19491.) +func RemoveAll(path string) error { + return removeAll(path) +} + +// IsEphemeralError reports whether err is one of the errors that the functions +// in this package attempt to mitigate. +// +// Errors considered ephemeral include: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +// +// This set may be expanded in the future; programs must not rely on the +// non-ephemerality of any given error. +func IsEphemeralError(err error) bool { + return isEphemeralError(err) +} diff --git a/robustio/robustio_darwin.go b/robustio/robustio_darwin.go new file mode 100644 index 00000000..99fd8ebc --- /dev/null +++ b/robustio/robustio_darwin.go @@ -0,0 +1,21 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package robustio + +import ( + "errors" + "syscall" +) + +const errFileNotFound = syscall.ENOENT + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == errFileNotFound + } + return false +} diff --git a/robustio/robustio_flaky.go b/robustio/robustio_flaky.go new file mode 100644 index 00000000..c56e36ca --- /dev/null +++ b/robustio/robustio_flaky.go @@ -0,0 +1,91 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows || darwin + +package robustio + +import ( + "errors" + "math/rand" + "os" + "syscall" + "time" +) + +const arbitraryTimeout = 2000 * time.Millisecond + +// retry retries ephemeral errors from f up to an arbitrary timeout +// to work around filesystem flakiness on Windows and Darwin. +func retry(f func() (err error, mayRetry bool)) error { + var ( + bestErr error + lowestErrno syscall.Errno + start time.Time + nextSleep time.Duration = 1 * time.Millisecond + ) + for { + err, mayRetry := f() + if err == nil || !mayRetry { + return err + } + + var errno syscall.Errno + if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) { + bestErr = err + lowestErrno = errno + } else if bestErr == nil { + bestErr = err + } + + if start.IsZero() { + start = time.Now() + } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { + break + } + time.Sleep(nextSleep) + nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) + } + + return bestErr +} + +// rename is like os.Rename, but retries ephemeral errors. +// +// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with +// MOVEFILE_REPLACE_EXISTING. +// +// Windows also provides a different system call, ReplaceFile, +// that provides similar semantics, but perhaps preserves more metadata. (The +// documentation on the differences between the two is very sparse.) +// +// Empirical error rates with MoveFileEx are lower under modest concurrency, so +// for now we're sticking with what the os package already provides. +func rename(oldpath, newpath string) (err error) { + return retry(func() (err error, mayRetry bool) { + err = os.Rename(oldpath, newpath) + return err, isEphemeralError(err) + }) +} + +// readFile is like os.ReadFile, but retries ephemeral errors. +func readFile(filename string) ([]byte, error) { + var b []byte + err := retry(func() (err error, mayRetry bool) { + b, err = os.ReadFile(filename) + + // Unlike in rename, we do not retry errFileNotFound here: it can occur + // as a spurious error, but the file may also genuinely not exist, so the + // increase in robustness is probably not worth the extra latency. + return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound) + }) + return b, err +} + +func removeAll(path string) error { + return retry(func() (err error, mayRetry bool) { + err = os.RemoveAll(path) + return err, isEphemeralError(err) + }) +} diff --git a/robustio/robustio_other.go b/robustio/robustio_other.go new file mode 100644 index 00000000..da9a46e4 --- /dev/null +++ b/robustio/robustio_other.go @@ -0,0 +1,27 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !darwin + +package robustio + +import ( + "os" +) + +func rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + +func readFile(filename string) ([]byte, error) { + return os.ReadFile(filename) +} + +func removeAll(path string) error { + return os.RemoveAll(path) +} + +func isEphemeralError(err error) bool { + return false +} diff --git a/robustio/robustio_windows.go b/robustio/robustio_windows.go new file mode 100644 index 00000000..961de4ef --- /dev/null +++ b/robustio/robustio_windows.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package robustio + +import ( + "errors" + "syscall" + + "github.com/rogpeppe/go-internal/internal/syscall/windows" +) + +const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + switch errno { + case syscall.ERROR_ACCESS_DENIED, + syscall.ERROR_FILE_NOT_FOUND, + windows.ERROR_SHARING_VIOLATION: + return true + } + } + return false +} diff --git a/semver/forward.go b/semver/forward.go new file mode 100644 index 00000000..ad557801 --- /dev/null +++ b/semver/forward.go @@ -0,0 +1,39 @@ +// Package semver is a thin forwarding layer on top of +// [golang.org/x/mod/semver]. See that package for documentation. +// +// Deprecated: use [golang.org/x/mod/semver] instead. +package semver + +import "golang.org/x/mod/semver" + +func IsValid(v string) bool { + return semver.IsValid(v) +} + +func Canonical(v string) string { + return semver.Canonical(v) +} + +func Major(v string) string { + return semver.Major(v) +} + +func MajorMinor(v string) string { + return semver.MajorMinor(v) +} + +func Prerelease(v string) string { + return semver.Prerelease(v) +} + +func Build(v string) string { + return semver.Build(v) +} + +func Compare(v, w string) int { + return semver.Compare(v, w) +} + +func Max(v, w string) string { + return semver.Max(v, w) +} diff --git a/semver/semver.go b/semver/semver.go deleted file mode 100644 index 4af7118e..00000000 --- a/semver/semver.go +++ /dev/null @@ -1,388 +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 semver implements comparison of semantic version strings. -// In this package, semantic version strings must begin with a leading "v", -// as in "v1.0.0". -// -// The general form of a semantic version string accepted by this package is -// -// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] -// -// where square brackets indicate optional parts of the syntax; -// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; -// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers -// using only alphanumeric characters and hyphens; and -// all-numeric PRERELEASE identifiers must not have leading zeros. -// -// This package follows Semantic Versioning 2.0.0 (see semver.org) -// with two exceptions. First, it requires the "v" prefix. Second, it recognizes -// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) -// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. -package semver - -// parsed returns the parsed form of a semantic version string. -type parsed struct { - major string - minor string - patch string - short string - prerelease string - build string - err string -} - -// IsValid reports whether v is a valid semantic version string. -func IsValid(v string) bool { - _, ok := parse(v) - return ok -} - -// Canonical returns the canonical formatting of the semantic version v. -// It fills in any missing .MINOR or .PATCH and discards build metadata. -// Two semantic versions compare equal only if their canonical formattings -// are identical strings. -// The canonical invalid semantic version is the empty string. -func Canonical(v string) string { - p, ok := parse(v) - if !ok { - return "" - } - if p.build != "" { - return v[:len(v)-len(p.build)] - } - if p.short != "" { - return v + p.short - } - return v -} - -// Major returns the major version prefix of the semantic version v. -// For example, Major("v2.1.0") == "v2". -// If v is an invalid semantic version string, Major returns the empty string. -func Major(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return v[:1+len(pv.major)] -} - -// MajorMinor returns the major.minor version prefix of the semantic version v. -// For example, MajorMinor("v2.1.0") == "v2.1". -// If v is an invalid semantic version string, MajorMinor returns the empty string. -func MajorMinor(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - i := 1 + len(pv.major) - if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { - return v[:j] - } - return v[:i] + "." + pv.minor -} - -// Prerelease returns the prerelease suffix of the semantic version v. -// For example, Prerelease("v2.1.0-pre+meta") == "-pre". -// If v is an invalid semantic version string, Prerelease returns the empty string. -func Prerelease(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return pv.prerelease -} - -// Build returns the build suffix of the semantic version v. -// For example, Build("v2.1.0+meta") == "+meta". -// If v is an invalid semantic version string, Build returns the empty string. -func Build(v string) string { - pv, ok := parse(v) - if !ok { - return "" - } - return pv.build -} - -// Compare returns an integer comparing two versions according to -// according to semantic version precedence. -// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. -// -// An invalid semantic version string is considered less than a valid one. -// All invalid semantic version strings compare equal to each other. -func Compare(v, w string) int { - pv, ok1 := parse(v) - pw, ok2 := parse(w) - if !ok1 && !ok2 { - return 0 - } - if !ok1 { - return -1 - } - if !ok2 { - return +1 - } - if c := compareInt(pv.major, pw.major); c != 0 { - return c - } - if c := compareInt(pv.minor, pw.minor); c != 0 { - return c - } - if c := compareInt(pv.patch, pw.patch); c != 0 { - return c - } - return comparePrerelease(pv.prerelease, pw.prerelease) -} - -// Max canonicalizes its arguments and then returns the version string -// that compares greater. -func Max(v, w string) string { - v = Canonical(v) - w = Canonical(w) - if Compare(v, w) > 0 { - return v - } - return w -} - -func parse(v string) (p parsed, ok bool) { - if v == "" || v[0] != 'v' { - p.err = "missing v prefix" - return - } - p.major, v, ok = parseInt(v[1:]) - if !ok { - p.err = "bad major version" - return - } - if v == "" { - p.minor = "0" - p.patch = "0" - p.short = ".0.0" - return - } - if v[0] != '.' { - p.err = "bad minor prefix" - ok = false - return - } - p.minor, v, ok = parseInt(v[1:]) - if !ok { - p.err = "bad minor version" - return - } - if v == "" { - p.patch = "0" - p.short = ".0" - return - } - if v[0] != '.' { - p.err = "bad patch prefix" - ok = false - return - } - p.patch, v, ok = parseInt(v[1:]) - if !ok { - p.err = "bad patch version" - return - } - if len(v) > 0 && v[0] == '-' { - p.prerelease, v, ok = parsePrerelease(v) - if !ok { - p.err = "bad prerelease" - return - } - } - if len(v) > 0 && v[0] == '+' { - p.build, v, ok = parseBuild(v) - if !ok { - p.err = "bad build" - return - } - } - if v != "" { - p.err = "junk on end" - ok = false - return - } - ok = true - return -} - -func parseInt(v string) (t, rest string, ok bool) { - if v == "" { - return - } - if v[0] < '0' || '9' < v[0] { - return - } - i := 1 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - if v[0] == '0' && i != 1 { - return - } - return v[:i], v[i:], true -} - -func parsePrerelease(v string) (t, rest string, ok bool) { - // "A pre-release version MAY be denoted by appending a hyphen and - // a series of dot separated identifiers immediately following the patch version. - // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. - // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." - if v == "" || v[0] != '-' { - return - } - i := 1 - start := 1 - for i < len(v) && v[i] != '+' { - if !isIdentChar(v[i]) && v[i] != '.' { - return - } - if v[i] == '.' { - if start == i || isBadNum(v[start:i]) { - return - } - start = i + 1 - } - i++ - } - if start == i || isBadNum(v[start:i]) { - return - } - return v[:i], v[i:], true -} - -func parseBuild(v string) (t, rest string, ok bool) { - if v == "" || v[0] != '+' { - return - } - i := 1 - start := 1 - for i < len(v) { - if !isIdentChar(v[i]) { - return - } - if v[i] == '.' { - if start == i { - return - } - start = i + 1 - } - i++ - } - if start == i { - return - } - return v[:i], v[i:], true -} - -func isIdentChar(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' -} - -func isBadNum(v string) bool { - i := 0 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - return i == len(v) && i > 1 && v[0] == '0' -} - -func isNum(v string) bool { - i := 0 - for i < len(v) && '0' <= v[i] && v[i] <= '9' { - i++ - } - return i == len(v) -} - -func compareInt(x, y string) int { - if x == y { - return 0 - } - if len(x) < len(y) { - return -1 - } - if len(x) > len(y) { - return +1 - } - if x < y { - return -1 - } else { - return +1 - } -} - -func comparePrerelease(x, y string) int { - // "When major, minor, and patch are equal, a pre-release version has - // lower precedence than a normal version. - // Example: 1.0.0-alpha < 1.0.0. - // Precedence for two pre-release versions with the same major, minor, - // and patch version MUST be determined by comparing each dot separated - // identifier from left to right until a difference is found as follows: - // identifiers consisting of only digits are compared numerically and - // identifiers with letters or hyphens are compared lexically in ASCII - // sort order. Numeric identifiers always have lower precedence than - // non-numeric identifiers. A larger set of pre-release fields has a - // higher precedence than a smaller set, if all of the preceding - // identifiers are equal. - // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < - // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." - if x == y { - return 0 - } - if x == "" { - return +1 - } - if y == "" { - return -1 - } - for x != "" && y != "" { - x = x[1:] // skip - or . - y = y[1:] // skip - or . - var dx, dy string - dx, x = nextIdent(x) - dy, y = nextIdent(y) - if dx != dy { - ix := isNum(dx) - iy := isNum(dy) - if ix != iy { - if ix { - return -1 - } else { - return +1 - } - } - if ix { - if len(dx) < len(dy) { - return -1 - } - if len(dx) > len(dy) { - return +1 - } - } - if dx < dy { - return -1 - } else { - return +1 - } - } - } - if x == "" { - return -1 - } else { - return +1 - } -} - -func nextIdent(x string) (dx, rest string) { - i := 0 - for i < len(x) && x[i] != '.' { - i++ - } - return x[:i], x[i:] -} diff --git a/semver/semver_test.go b/semver/semver_test.go deleted file mode 100644 index 96b64a58..00000000 --- a/semver/semver_test.go +++ /dev/null @@ -1,182 +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 semver - -import ( - "strings" - "testing" -) - -var tests = []struct { - in string - out string -}{ - {"bad", ""}, - {"v1-alpha.beta.gamma", ""}, - {"v1-pre", ""}, - {"v1+meta", ""}, - {"v1-pre+meta", ""}, - {"v1.2-pre", ""}, - {"v1.2+meta", ""}, - {"v1.2-pre+meta", ""}, - {"v1.0.0-alpha", "v1.0.0-alpha"}, - {"v1.0.0-alpha.1", "v1.0.0-alpha.1"}, - {"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"}, - {"v1.0.0-beta", "v1.0.0-beta"}, - {"v1.0.0-beta.2", "v1.0.0-beta.2"}, - {"v1.0.0-beta.11", "v1.0.0-beta.11"}, - {"v1.0.0-rc.1", "v1.0.0-rc.1"}, - {"v1", "v1.0.0"}, - {"v1.0", "v1.0.0"}, - {"v1.0.0", "v1.0.0"}, - {"v1.2", "v1.2.0"}, - {"v1.2.0", "v1.2.0"}, - {"v1.2.3-456", "v1.2.3-456"}, - {"v1.2.3-456.789", "v1.2.3-456.789"}, - {"v1.2.3-456-789", "v1.2.3-456-789"}, - {"v1.2.3-456a", "v1.2.3-456a"}, - {"v1.2.3-pre", "v1.2.3-pre"}, - {"v1.2.3-pre+meta", "v1.2.3-pre"}, - {"v1.2.3-pre.1", "v1.2.3-pre.1"}, - {"v1.2.3-zzz", "v1.2.3-zzz"}, - {"v1.2.3", "v1.2.3"}, - {"v1.2.3+meta", "v1.2.3"}, - {"v1.2.3+meta-pre", "v1.2.3"}, -} - -func TestIsValid(t *testing.T) { - for _, tt := range tests { - ok := IsValid(tt.in) - if ok != (tt.out != "") { - t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok) - } - } -} - -func TestCanonical(t *testing.T) { - for _, tt := range tests { - out := Canonical(tt.in) - if out != tt.out { - t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out) - } - } -} - -func TestMajor(t *testing.T) { - for _, tt := range tests { - out := Major(tt.in) - want := "" - if i := strings.Index(tt.out, "."); i >= 0 { - want = tt.out[:i] - } - if out != want { - t.Errorf("Major(%q) = %q, want %q", tt.in, out, want) - } - } -} - -func TestMajorMinor(t *testing.T) { - for _, tt := range tests { - out := MajorMinor(tt.in) - var want string - if tt.out != "" { - want = tt.in - if i := strings.Index(want, "+"); i >= 0 { - want = want[:i] - } - if i := strings.Index(want, "-"); i >= 0 { - want = want[:i] - } - switch strings.Count(want, ".") { - case 0: - want += ".0" - case 1: - // ok - case 2: - want = want[:strings.LastIndex(want, ".")] - } - } - if out != want { - t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want) - } - } -} - -func TestPrerelease(t *testing.T) { - for _, tt := range tests { - pre := Prerelease(tt.in) - var want string - if tt.out != "" { - if i := strings.Index(tt.out, "-"); i >= 0 { - want = tt.out[i:] - } - } - if pre != want { - t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want) - } - } -} - -func TestBuild(t *testing.T) { - for _, tt := range tests { - build := Build(tt.in) - var want string - if tt.out != "" { - if i := strings.Index(tt.in, "+"); i >= 0 { - want = tt.in[i:] - } - } - if build != want { - t.Errorf("Build(%q) = %q, want %q", tt.in, build, want) - } - } -} - -func TestCompare(t *testing.T) { - for i, ti := range tests { - for j, tj := range tests { - cmp := Compare(ti.in, tj.in) - var want int - if ti.out == tj.out { - want = 0 - } else if i < j { - want = -1 - } else { - want = +1 - } - if cmp != want { - t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want) - } - } - } -} - -func TestMax(t *testing.T) { - for i, ti := range tests { - for j, tj := range tests { - max := Max(ti.in, tj.in) - want := Canonical(ti.in) - if i < j { - want = Canonical(tj.in) - } - if max != want { - t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want) - } - } - } -} - -var ( - v1 = "v1.0.0+metadata-dash" - v2 = "v1.0.0+metadata-dash1" -) - -func BenchmarkCompare(b *testing.B) { - for i := 0; i < b.N; i++ { - if Compare(v1, v2) != 0 { - b.Fatalf("bad compare") - } - } -} diff --git a/testenv/testenv.go b/testenv/testenv.go index 8f69fe0d..bcac6a50 100644 --- a/testenv/testenv.go +++ b/testenv/testenv.go @@ -30,7 +30,7 @@ func Builder() string { return os.Getenv("GO_BUILDER_NAME") } -// HasGoBuild reports whether the current system can build programs with ``go build'' +// HasGoBuild reports whether the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. func HasGoBuild() bool { if os.Getenv("GO_GCFLAGS") != "" { @@ -51,7 +51,7 @@ func HasGoBuild() bool { return true } -// MustHaveGoBuild checks that the current system can build programs with ``go build'' +// MustHaveGoBuild checks that the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. // If not, MustHaveGoBuild calls t.Skip with an explanation. func MustHaveGoBuild(t testing.TB) { @@ -63,13 +63,13 @@ func MustHaveGoBuild(t testing.TB) { } } -// HasGoRun reports whether the current system can run programs with ``go run.'' +// HasGoRun reports whether the current system can run programs with “go run.” func HasGoRun() bool { // For now, having go run and having go build are the same. return HasGoBuild() } -// MustHaveGoRun checks that the current system can run programs with ``go run.'' +// MustHaveGoRun checks that the current system can run programs with “go run.” // If not, MustHaveGoRun calls t.Skip with an explanation. func MustHaveGoRun(t testing.TB) { if !HasGoRun() { diff --git a/testenv/testenv_cgo.go b/testenv/testenv_cgo.go index 02f08f57..7426a29c 100644 --- a/testenv/testenv_cgo.go +++ b/testenv/testenv_cgo.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build cgo -// +build cgo package testenv diff --git a/testenv/testenv_notwin.go b/testenv/testenv_notwin.go index a1ae92c0..1484058f 100644 --- a/testenv/testenv_notwin.go +++ b/testenv/testenv_notwin.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !windows -// +build !windows package testenv diff --git a/testscript/clonefile.go b/testscript/clonefile.go new file mode 100644 index 00000000..be49a2c8 --- /dev/null +++ b/testscript/clonefile.go @@ -0,0 +1,10 @@ +//go:build unix && !darwin + +package testscript + +import "os" + +// cloneFile makes a clone of a file via a hard link. +func cloneFile(from, to string) error { + return os.Link(from, to) +} diff --git a/testscript/clonefile_darwin.go b/testscript/clonefile_darwin.go new file mode 100644 index 00000000..79645bc2 --- /dev/null +++ b/testscript/clonefile_darwin.go @@ -0,0 +1,8 @@ +package testscript + +import "golang.org/x/sys/unix" + +// cloneFile makes a clone of a file via MacOS's `clonefile` syscall. +func cloneFile(from, to string) error { + return unix.Clonefile(from, to, 0) +} diff --git a/testscript/clonefile_other.go b/testscript/clonefile_other.go new file mode 100644 index 00000000..1748cafe --- /dev/null +++ b/testscript/clonefile_other.go @@ -0,0 +1,12 @@ +//go:build !unix + +package testscript + +import "fmt" + +// cloneFile does not attempt anything on Windows, as hard links on it have +// led to "access denied" errors when deleting files at the end of a test. +// We haven't tested platforms like plan9 or wasm/wasi. +func cloneFile(from, to string) error { + return fmt.Errorf("unavailable") +} diff --git a/testscript/cmd.go b/testscript/cmd.go index 2cf63f74..dd8c1365 100644 --- a/testscript/cmd.go +++ b/testscript/cmd.go @@ -13,10 +13,12 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strconv" "strings" - "github.com/pkg/diff" + "github.com/rogpeppe/go-internal/diff" + "github.com/rogpeppe/go-internal/testscript/internal/pty" "github.com/rogpeppe/go-internal/txtar" ) @@ -41,6 +43,8 @@ var scriptCmds = map[string]func(*TestScript, bool, []string){ "stderr": (*TestScript).cmdStderr, "stdin": (*TestScript).cmdStdin, "stdout": (*TestScript).cmdStdout, + "ttyin": (*TestScript).cmdTtyin, + "ttyout": (*TestScript).cmdTtyout, "stop": (*TestScript).cmdStop, "symlink": (*TestScript).cmdSymlink, "unix2dos": (*TestScript).cmdUNIX2DOS, @@ -141,22 +145,9 @@ func (ts *TestScript) doCmdCmp(neg bool, args []string, env bool) { // update the script. } - // pkg/diff is quadratic at the moment. - // If the product of the number of lines in the inputs is too large, - // don't call pkg.Diff at all as it might take tons of memory or time. - // We found one million to be reasonable for an average laptop. - const maxLineDiff = 1_000_000 - if strings.Count(text1, "\n")*strings.Count(text2, "\n") > maxLineDiff { - ts.Fatalf("large files %s and %s differ", name1, name2) - return - } - - var sb strings.Builder - if err := diff.Text(name1, name2, text1, text2, &sb); err != nil { - ts.Check(err) - } + unifiedDiff := diff.Diff(name1, []byte(text1), name2, []byte(text2)) - ts.Logf("%s", sb.String()) + ts.Logf("%s", unifiedDiff) ts.Fatalf("%s and %s differ", name1, name2) } @@ -191,6 +182,10 @@ func (ts *TestScript) cmdCp(neg bool, args []string) { src = arg data = []byte(ts.stderr) mode = 0o666 + case "ttyout": + src = arg + data = []byte(ts.ttyout) + mode = 0o666 default: src = ts.MkAbs(arg) info, err := os.Stat(src) @@ -253,7 +248,7 @@ func (ts *TestScript) cmdExec(neg bool, args []string) { if err == nil { wait := make(chan struct{}) go func() { - ctxWait(ts.ctxt, cmd) + waitOrStop(ts.ctxt, cmd, -1) close(wait) }() ts.background = append(ts.background, backgroundCmd{bgName, cmd, wait, neg}) @@ -395,6 +390,9 @@ func (ts *TestScript) cmdStdin(neg bool, args []string) { if len(args) != 1 { ts.Fatalf("usage: stdin filename") } + if ts.stdinPty { + ts.Fatalf("conflicting use of 'stdin' and 'ttyin -stdin'") + } ts.stdin = ts.ReadFile(args[0]) } @@ -414,6 +412,37 @@ func (ts *TestScript) cmdGrep(neg bool, args []string) { scriptMatch(ts, neg, args, "", "grep") } +func (ts *TestScript) cmdTtyin(neg bool, args []string) { + if !pty.Supported { + ts.Fatalf("unsupported: ttyin on %s", runtime.GOOS) + } + if neg { + ts.Fatalf("unsupported: ! ttyin") + } + switch len(args) { + case 1: + ts.ttyin = ts.ReadFile(args[0]) + case 2: + if args[0] != "-stdin" { + ts.Fatalf("usage: ttyin [-stdin] filename") + } + if ts.stdin != "" { + ts.Fatalf("conflicting use of 'stdin' and 'ttyin -stdin'") + } + ts.stdinPty = true + ts.ttyin = ts.ReadFile(args[1]) + default: + ts.Fatalf("usage: ttyin [-stdin] filename") + } + if ts.ttyin == "" { + ts.Fatalf("tty input file is empty") + } +} + +func (ts *TestScript) cmdTtyout(neg bool, args []string) { + scriptMatch(ts, neg, args, ts.ttyout, "ttyout") +} + // stop stops execution of the test (marking it passed). func (ts *TestScript) cmdStop(neg bool, args []string) { if neg { diff --git a/testscript/cover.go b/testscript/cover.go deleted file mode 100644 index 181605b0..00000000 --- a/testscript/cover.go +++ /dev/null @@ -1,280 +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 testscript - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "sync/atomic" - "testing" -) - -// mergeCoverProfile merges the coverage information in f into -// cover. It assumes that the coverage information in f is -// always produced from the same binary for every call. -func mergeCoverProfile(cover *testing.Cover, path string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - scanner, err := newProfileScanner(f) - if err != nil { - return err - } - if scanner.Mode() != testing.CoverMode() { - return errors.New("unexpected coverage mode in subcommand") - } - if cover.Mode == "" { - cover.Mode = scanner.Mode() - } - isCount := cover.Mode == "count" - if cover.Counters == nil { - cover.Counters = make(map[string][]uint32) - cover.Blocks = make(map[string][]testing.CoverBlock) - } - - // Note that we rely on the fact that the coverage is written - // out file-by-file, with all blocks for a file in sequence. - var ( - filename string - blockId uint32 - counters []uint32 - blocks []testing.CoverBlock - ) - flush := func() { - if len(counters) > 0 { - cover.Counters[filename] = counters - cover.Blocks[filename] = blocks - } - } - for scanner.Scan() { - block := scanner.Block() - if scanner.Filename() != filename { - flush() - filename = scanner.Filename() - counters = cover.Counters[filename] - blocks = cover.Blocks[filename] - blockId = 0 - } else { - blockId++ - } - if int(blockId) >= len(counters) { - counters = append(counters, block.Count) - blocks = append(blocks, block.CoverBlock) - continue - } - // TODO check that block.CoverBlock == blocks[blockId] ? - if isCount { - counters[blockId] += block.Count - } else { - counters[blockId] |= block.Count - } - } - flush() - if scanner.Err() != nil { - return fmt.Errorf("error scanning profile: %v", err) - } - return nil -} - -func finalizeCoverProfile(dir string) error { - // Merge all the coverage profiles written by test binary sub-processes, - // such as those generated by executions of commands. - var cover testing.Cover - if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.Mode().IsRegular() { - return nil - } - if err := mergeCoverProfile(&cover, path); err != nil { - return fmt.Errorf("cannot merge coverage profile from %v: %v", path, err) - } - return nil - }); err != nil { - return err - } - if err := os.RemoveAll(dir); err != nil { - // The RemoveAll seems to fail very rarely, with messages like - // "directory not empty". It's unclear why. - // For now, if it happens again, try to print a bit more info. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err == nil && !info.IsDir() { - fmt.Fprintln(os.Stderr, "non-directory found after RemoveAll:", path) - } - return nil - }) - return err - } - - // We need to include our own top-level coverage profile too. - cprof := coverProfile() - if err := mergeCoverProfile(&cover, cprof); err != nil { - return fmt.Errorf("cannot merge coverage profile from %v: %v", cprof, err) - } - - // Finally, write the resulting merged profile. - f, err := os.Create(cprof) - if err != nil { - return fmt.Errorf("cannot create cover profile: %v", err) - } - defer f.Close() - w := bufio.NewWriter(f) - if err := writeCoverProfile1(w, cover); err != nil { - return err - } - if err := w.Flush(); err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - return nil -} - -func writeCoverProfile1(w io.Writer, cover testing.Cover) error { - fmt.Fprintf(w, "mode: %s\n", cover.Mode) - var active, total int64 - var count uint32 - for name, counts := range cover.Counters { - blocks := cover.Blocks[name] - for i := range counts { - stmts := int64(blocks[i].Stmts) - total += stmts - count = atomic.LoadUint32(&counts[i]) // For -mode=atomic. - if count > 0 { - active += stmts - } - _, err := fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n", name, - blocks[i].Line0, blocks[i].Col0, - blocks[i].Line1, blocks[i].Col1, - stmts, - count, - ) - if err != nil { - return err - } - } - } - if total == 0 { - total = 1 - } - fmt.Printf("total coverage: %.1f%% of statements%s\n", 100*float64(active)/float64(total), cover.CoveredPackages) - return nil -} - -type profileScanner struct { - mode string - err error - scanner *bufio.Scanner - filename string - block coverBlock -} - -type coverBlock struct { - testing.CoverBlock - Count uint32 -} - -var profileLineRe = regexp.MustCompile(`^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$`) - -func toInt(s string) int { - i, err := strconv.Atoi(s) - if err != nil { - panic(err) - } - return i -} - -func newProfileScanner(r io.Reader) (*profileScanner, error) { - s := &profileScanner{ - scanner: bufio.NewScanner(r), - } - // First line is "mode: foo", where foo is "set", "count", or "atomic". - // Rest of file is in the format - // encoding/base64/base64.go:34.44,37.40 3 1 - // where the fields are: name.go:line.column,line.column numberOfStatements count - if !s.scanner.Scan() { - return nil, fmt.Errorf("no lines found in profile: %v", s.Err()) - } - line := s.scanner.Text() - mode := strings.TrimPrefix(line, "mode: ") - if len(mode) == len(line) { - return nil, fmt.Errorf("bad mode line %q", line) - } - s.mode = mode - return s, nil -} - -// Mode returns the profile's coverage mode (one of "atomic", "count: -// or "set"). -func (s *profileScanner) Mode() string { - return s.mode -} - -// Err returns any error encountered when scanning a profile. -func (s *profileScanner) Err() error { - if s.err == io.EOF { - return nil - } - return s.err -} - -// Block returns the most recently scanned profile block, or the zero -// block if Scan has not been called or has returned false. -func (s *profileScanner) Block() coverBlock { - if s.err == nil { - return s.block - } - return coverBlock{} -} - -// Filename returns the filename of the most recently scanned profile -// block, or the empty string if Scan has not been called or has -// returned false. -func (s *profileScanner) Filename() string { - if s.err == nil { - return s.filename - } - return "" -} - -// Scan scans the next line in a coverage profile and reports whether -// a line was found. -func (s *profileScanner) Scan() bool { - if s.err != nil { - return false - } - if !s.scanner.Scan() { - s.err = io.EOF - return false - } - m := profileLineRe.FindStringSubmatch(s.scanner.Text()) - if m == nil { - s.err = fmt.Errorf("line %q doesn't match expected format %v", m, profileLineRe) - return false - } - s.filename = m[1] - s.block = coverBlock{ - CoverBlock: testing.CoverBlock{ - Line0: uint32(toInt(m[2])), - Col0: uint16(toInt(m[3])), - Line1: uint32(toInt(m[4])), - Col1: uint16(toInt(m[5])), - Stmts: uint16(toInt(m[6])), - }, - Count: uint32(toInt(m[7])), - } - return true -} diff --git a/testscript/doc.go b/testscript/doc.go index d1dfc9dc..1d23228d 100644 --- a/testscript/doc.go +++ b/testscript/doc.go @@ -38,7 +38,7 @@ In general script files should have short names: a few words, not whole sentence The first word should be the general category of behavior being tested, often the name of a subcommand to be tested or a concept (vendor, pattern). -Each script is a text archive (go doc github.com/rogpeppe/go-internal/txtar). +Each script is a text archive (go doc golang.org/x/tools/txtar). The script begins with an actual command script to run followed by the content of zero or more supporting files to create in the script's temporary file system before it starts executing. @@ -205,6 +205,16 @@ The predefined commands are: Apply the grep command (see above) to the standard output from the most recent exec or wait command. + - ttyin [-stdin] file + Attach the next exec command to a controlling pseudo-terminal, and use the + contents of the given file as the raw terminal input. If -stdin is specified, + also attach the terminal to standard input. + Note that this does not attach the terminal to standard output/error. + + - [!] ttyout [-count=N] pattern + Apply the grep command (see above) to the raw controlling terminal output + from the most recent exec command. + - stop [message] Stop the test early (marking it as passing), including the message if given. diff --git a/testscript/envvarname.go b/testscript/envvarname.go deleted file mode 100644 index 15e41f2b..00000000 --- a/testscript/envvarname.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build !windows -// +build !windows - -package testscript - -func envvarname(k string) string { - return k -} diff --git a/testscript/envvarname_windows.go b/testscript/envvarname_windows.go deleted file mode 100644 index 7c6a3b85..00000000 --- a/testscript/envvarname_windows.go +++ /dev/null @@ -1,7 +0,0 @@ -package testscript - -import "strings" - -func envvarname(k string) string { - return strings.ToLower(k) -} diff --git a/testscript/exe.go b/testscript/exe.go index 46c611ae..95e90b92 100644 --- a/testscript/exe.go +++ b/testscript/exe.go @@ -5,9 +5,6 @@ package testscript import ( - cryptorand "crypto/rand" - "flag" - "fmt" "io" "io/ioutil" "log" @@ -24,15 +21,8 @@ type TestingM interface { Run() int } -var ignoreMissedCoverage = false - -// IgnoreMissedCoverage causes any missed coverage information -// (for example when a function passed to RunMain -// calls os.Exit, for example) to be ignored. -// This function should be called before calling RunMain. -func IgnoreMissedCoverage() { - ignoreMissedCoverage = true -} +// Deprecated: this option is no longer used. +func IgnoreMissedCoverage() {} // RunMain should be called within a TestMain function to allow // subcommands to be run in the testscript context. @@ -84,26 +74,6 @@ func RunMain(m TestingM, commands map[string]func() int) (exitCode int) { } os.Setenv("PATH", bindir+string(filepath.ListSeparator)+os.Getenv("PATH")) - flag.Parse() - // If we are collecting a coverage profile, set up a shared - // directory for all executed test binary sub-processes to write - // their profiles to. Before finishing, we'll merge all of those - // profiles into the main profile. - if coverProfile() != "" { - coverdir := filepath.Join(tmpdir, "cover") - if err := os.MkdirAll(coverdir, 0o777); err != nil { - log.Printf("could not set up cover directory: %v", err) - return 2 - } - os.Setenv("TESTSCRIPT_COVER_DIR", coverdir) - defer func() { - if err := finalizeCoverProfile(coverdir); err != nil { - log.Printf("cannot merge cover profiles: %v", err) - exitCode = 2 - } - }() - } - // We're not in a subcommand. for name := range commands { name := name @@ -131,31 +101,7 @@ func RunMain(m TestingM, commands map[string]func() int) (exitCode int) { } // The command being registered is being invoked, so run it, then exit. os.Args[0] = cmdName - coverdir := os.Getenv("TESTSCRIPT_COVER_DIR") - if coverdir == "" { - // No coverage, act as normal. - return mainf() - } - - // For a command "foo", write ${TESTSCRIPT_COVER_DIR}/foo-${RANDOM}. - // Note that we do not use ioutil.TempFile as that creates the file. - // In this case, we want to leave it to -test.coverprofile to create the - // file, as otherwise we could end up with an empty file. - // Later, when merging profiles, trying to merge an empty file would - // result in a confusing error. - rnd, err := nextRandom() - if err != nil { - log.Printf("could not obtain random number: %v", err) - return 2 - } - cprof := filepath.Join(coverdir, fmt.Sprintf("%s-%x", cmdName, rnd)) - return runCoverSubcommand(cprof, mainf) -} - -func nextRandom() ([]byte, error) { - p := make([]byte, 6) - _, err := cryptorand.Read(p) - return p, err + return mainf() } // copyBinary makes a copy of a binary to a new location. It is used as part of @@ -171,15 +117,12 @@ func nextRandom() ([]byte, error) { // Second, symlinks might not be available on some environments, so we have to // implement a "full copy" fallback anyway. // -// However, we do try to use a hard link, since that will probably work on most +// However, we do try to use cloneFile, since that will probably work on most // unix-like setups. Note that "go test" also places test binaries in the -// system's temporary directory, like we do. We don't use hard links on Windows, -// as that can lead to "access denied" errors when removing. +// system's temporary directory, like we do. func copyBinary(from, to string) error { - if runtime.GOOS != "windows" { - if err := os.Link(from, to); err == nil { - return nil - } + if err := cloneFile(from, to); err == nil { + return nil } writer, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE, 0o777) if err != nil { @@ -196,109 +139,3 @@ func copyBinary(from, to string) error { _, err = io.Copy(writer, reader) return err } - -// runCoverSubcommand runs the given function, then writes any generated -// coverage information to the cprof file. -// This is called inside a separately run executable. -func runCoverSubcommand(cprof string, mainf func() int) (exitCode int) { - // Change the error handling mode to PanicOnError - // so that in the common case of calling flag.Parse in main we'll - // be able to catch the panic instead of just exiting. - flag.CommandLine.Init(flag.CommandLine.Name(), flag.PanicOnError) - defer func() { - panicErr := recover() - if err, ok := panicErr.(error); ok { - // The flag package will already have printed this error, assuming, - // that is, that the error was created in the flag package. - // TODO check the stack to be sure it was actually raised by the flag package. - exitCode = 2 - if err == flag.ErrHelp { - exitCode = 0 - } - panicErr = nil - } - // Set os.Args so that flag.Parse will tell testing the correct - // coverprofile setting. Unfortunately this isn't sufficient because - // the testing oackage explicitly avoids calling flag.Parse again - // if flag.Parsed returns true, so we the coverprofile value directly - // too. - os.Args = []string{os.Args[0], "-test.coverprofile=" + cprof} - setCoverProfile(cprof) - - // Suppress the chatty coverage and test report. - devNull, err := os.Open(os.DevNull) - if err != nil { - panic(err) - } - os.Stdout = devNull - os.Stderr = devNull - - // Run MainStart (recursively, but it we should be ok) with no tests - // so that it writes the coverage profile. - m := mainStart() - if code := m.Run(); code != 0 && exitCode == 0 { - exitCode = code - } - if _, err := os.Stat(cprof); err != nil { - log.Printf("failed to write coverage profile %q", cprof) - } - if panicErr != nil { - // The error didn't originate from the flag package (we know that - // flag.PanicOnError causes an error value that implements error), - // so carry on panicking. - panic(panicErr) - } - }() - return mainf() -} - -func coverProfileFlag() flag.Getter { - f := flag.CommandLine.Lookup("test.coverprofile") - if f == nil { - // We've imported testing so it definitely should be there. - panic("cannot find test.coverprofile flag") - } - return f.Value.(flag.Getter) -} - -func coverProfile() string { - return coverProfileFlag().Get().(string) -} - -func setCoverProfile(cprof string) { - coverProfileFlag().Set(cprof) -} - -type nopTestDeps struct{} - -func (nopTestDeps) MatchString(pat, str string) (result bool, err error) { - return false, nil -} - -func (nopTestDeps) StartCPUProfile(w io.Writer) error { - return nil -} - -func (nopTestDeps) StopCPUProfile() {} - -func (nopTestDeps) WriteProfileTo(name string, w io.Writer, debug int) error { - return nil -} - -func (nopTestDeps) ImportPath() string { - return "" -} -func (nopTestDeps) StartTestLog(w io.Writer) {} - -func (nopTestDeps) StopTestLog() error { - return nil -} - -// Note: WriteHeapProfile is needed for Go 1.10 but not Go 1.11. -func (nopTestDeps) WriteHeapProfile(io.Writer) error { - // Not needed for Go 1.10. - return nil -} - -// Note: SetPanicOnExit0 was added in Go 1.16. -func (nopTestDeps) SetPanicOnExit0(bool) {} diff --git a/testscript/exe_go118.go b/testscript/exe_go118.go deleted file mode 100644 index f65a5527..00000000 --- a/testscript/exe_go118.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build go1.18 -// +build go1.18 - -package testscript - -import ( - "reflect" - "testing" - "time" -) - -func mainStart() *testing.M { - // Note: testing.MainStart acquired an extra argument in Go 1.18. - return testing.MainStart(nopTestDeps{}, nil, nil, nil, nil) -} - -// Note: corpusEntry is an anonymous struct type used by some method stubs. -type corpusEntry = struct { - Parent string - Path string - Data []byte - Values []interface{} - Generation int - IsSeed bool -} - -// Note: CoordinateFuzzing was added in Go 1.18. -func (nopTestDeps) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error { - return nil -} - -// Note: RunFuzzWorker was added in Go 1.18. -func (nopTestDeps) RunFuzzWorker(func(corpusEntry) error) error { - return nil -} - -// Note: ReadCorpus was added in Go 1.18. -func (nopTestDeps) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) { - return nil, nil -} - -// Note: CheckCorpus was added in Go 1.18. -func (nopTestDeps) CheckCorpus([]interface{}, []reflect.Type) error { - return nil -} - -// Note: ResetCoverage was added in Go 1.18. -func (nopTestDeps) ResetCoverage() {} - -// Note: SnapshotCoverage was added in Go 1.18. -func (nopTestDeps) SnapshotCoverage() {} diff --git a/testscript/exe_pre_go1.18.go b/testscript/exe_pre_go1.18.go deleted file mode 100644 index e9323a22..00000000 --- a/testscript/exe_pre_go1.18.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !go1.18 -// +build !go1.18 - -package testscript - -import ( - "testing" -) - -func mainStart() *testing.M { - return testing.MainStart(nopTestDeps{}, nil, nil, nil) -} diff --git a/testscript/internal/pty/pty.go b/testscript/internal/pty/pty.go new file mode 100644 index 00000000..39409a59 --- /dev/null +++ b/testscript/internal/pty/pty.go @@ -0,0 +1,62 @@ +//go:build linux || darwin +// +build linux darwin + +package pty + +import ( + "fmt" + "os" + "os/exec" + "syscall" +) + +const Supported = true + +func SetCtty(cmd *exec.Cmd, tty *os.File) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setctty: true, + Setsid: true, + Ctty: 3, + } + cmd.ExtraFiles = []*os.File{tty} +} + +func Open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, fmt.Errorf("failed to open pty multiplexer: %v", err) + } + defer func() { + if err != nil { + p.Close() + } + }() + + name, err := ptyName(p) + if err != nil { + return nil, nil, fmt.Errorf("failed to obtain tty name: %v", err) + } + + if err := ptyGrant(p); err != nil { + return nil, nil, fmt.Errorf("failed to grant pty: %v", err) + } + + if err := ptyUnlock(p); err != nil { + return nil, nil, fmt.Errorf("failed to unlock pty: %v", err) + } + + t, err := os.OpenFile(name, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + return nil, nil, fmt.Errorf("failed to open TTY: %v", err) + } + + return p, t, nil +} + +func ioctl(f *os.File, name string, cmd, ptr uintptr) error { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), cmd, ptr) + if err != 0 { + return fmt.Errorf("%s ioctl failed: %v", name, err) + } + return nil +} diff --git a/testscript/internal/pty/pty_darwin.go b/testscript/internal/pty/pty_darwin.go new file mode 100644 index 00000000..fe307d06 --- /dev/null +++ b/testscript/internal/pty/pty_darwin.go @@ -0,0 +1,32 @@ +package pty + +import ( + "bytes" + "os" + "syscall" + "unsafe" +) + +func ptyName(f *os.File) (string, error) { + // Parameter length is encoded in the low 13 bits of the top word. + // See https://github.com/apple/darwin-xnu/blob/2ff845c2e0/bsd/sys/ioccom.h#L69-L77 + const IOCPARM_MASK = 0x1fff + const TIOCPTYGNAME_PARM_LEN = (syscall.TIOCPTYGNAME >> 16) & IOCPARM_MASK + out := make([]byte, TIOCPTYGNAME_PARM_LEN) + + err := ioctl(f, "TIOCPTYGNAME", syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&out[0]))) + if err != nil { + return "", err + } + + i := bytes.IndexByte(out, 0x00) + return string(out[:i]), nil +} + +func ptyGrant(f *os.File) error { + return ioctl(f, "TIOCPTYGRANT", syscall.TIOCPTYGRANT, 0) +} + +func ptyUnlock(f *os.File) error { + return ioctl(f, "TIOCPTYUNLK", syscall.TIOCPTYUNLK, 0) +} diff --git a/testscript/internal/pty/pty_linux.go b/testscript/internal/pty/pty_linux.go new file mode 100644 index 00000000..6e77b3a2 --- /dev/null +++ b/testscript/internal/pty/pty_linux.go @@ -0,0 +1,26 @@ +package pty + +import ( + "os" + "strconv" + "syscall" + "unsafe" +) + +func ptyName(f *os.File) (string, error) { + var out uint + err := ioctl(f, "TIOCGPTN", syscall.TIOCGPTN, uintptr(unsafe.Pointer(&out))) + if err != nil { + return "", err + } + return "/dev/pts/" + strconv.Itoa(int(out)), nil +} + +func ptyGrant(f *os.File) error { + return nil +} + +func ptyUnlock(f *os.File) error { + var zero int + return ioctl(f, "TIOCSPTLCK", syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&zero))) +} diff --git a/testscript/internal/pty/pty_unsupported.go b/testscript/internal/pty/pty_unsupported.go new file mode 100644 index 00000000..457394ee --- /dev/null +++ b/testscript/internal/pty/pty_unsupported.go @@ -0,0 +1,21 @@ +//go:build !linux && !darwin +// +build !linux,!darwin + +package pty + +import ( + "fmt" + "os" + "os/exec" + "runtime" +) + +const Supported = false + +func SetCtty(cmd *exec.Cmd, tty *os.File) error { + panic("SetCtty called on unsupported platform") +} + +func Open() (pty, tty *os.File, err error) { + return nil, nil, fmt.Errorf("pty unsupported on %s", runtime.GOOS) +} diff --git a/testscript/testdata/big_diff.txt b/testscript/testdata/big_diff.txt index 43240c02..f3effc00 100644 --- a/testscript/testdata/big_diff.txt +++ b/testscript/testdata/big_diff.txt @@ -8,7 +8,2064 @@ cmpenv stdout stdout.golden -- stdout.golden -- > cmp a b -FAIL: $$WORK${/}dir${/}script.txt:1: large files a and b differ +diff a b +--- a ++++ b +@@ -1,1017 +1,1036 @@ + 0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 +-E07BDA2D3E411F8AE1E2B2F3A7D95342 +-840F2018A642B9956896793DE31E7059 +-9D8DCB6C73C034185419A3CA355ABEDA +-13A225190669971F58E1B97AC92D8701 +-A7361785190939C924748BD5AAD2C38D +-0A846FFACD16E92D74B1CFC38263DAED +-5A85FE36ECB3B9369E03465338F1D2F4 +-80D3C2C75E495EFFAEA9E56FA262D1C8 +-CD5206C016A0CC44CCE3187A128B0654 +-18A23CE2287673019BE407FB31A4A0C2 +-BD5C67BC3D29256E1BDEB78F5A43DF46 +-336B82DE9B7BE168E8DFCE82310613AE +-51C1CCA42F66F5B1F4C17396DE8EDAA7 +-402A9501F16DE1B9FA81CFCDF3F54392 ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++0A846FFACD16E92D74B1CFC38263DAED ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++13A225190669971F58E1B97AC92D8701 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++18A23CE2287673019BE407FB31A4A0C2 ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++336B82DE9B7BE168E8DFCE82310613AE ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++402A9501F16DE1B9FA81CFCDF3F54392 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++51C1CCA42F66F5B1F4C17396DE8EDAA7 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++5A85FE36ECB3B9369E03465338F1D2F4 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++80D3C2C75E495EFFAEA9E56FA262D1C8 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++840F2018A642B9956896793DE31E7059 ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++9D8DCB6C73C034185419A3CA355ABEDA ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++A7361785190939C924748BD5AAD2C38D ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++BD5C67BC3D29256E1BDEB78F5A43DF46 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 ++CD5206C016A0CC44CCE3187A128B0654 + +FAIL: $$WORK${/}dir${/}script.txt:1: a and b differ -- dir/script.txt -- >cmp a b > diff --git a/testscript/testdata/cmd_stdout_stderr.txt b/testscript/testdata/cmd_stdout_stderr.txt new file mode 100644 index 00000000..e9e0791f --- /dev/null +++ b/testscript/testdata/cmd_stdout_stderr.txt @@ -0,0 +1,39 @@ +# Verify that when we don't update stdout when we don't attempt to write via Stdout() +fprintargs stdout hello stdout from fprintargs +stdout 'hello stdout from fprintargs' +echoandexit 0 +stdout 'hello stdout from fprintargs' + +# Verify that when we don't update stderr when we don't attempt to write via Stderr() +fprintargs stderr hello stderr from fprintargs +stderr 'hello stderr from fprintargs' +echoandexit 0 +stderr 'hello stderr from fprintargs' + +# Verify that we do update stdout when we attempt to write via Stdout() or Stderr() +fprintargs stdout hello stdout from fprintargs +stdout 'hello stdout from fprintargs' +! stderr .+ +echoandexit 0 'hello stdout from echoandexit' +stdout 'hello stdout from echoandexit' +! stderr .+ +fprintargs stdout hello stdout from fprintargs +stdout 'hello stdout from fprintargs' +! stderr .+ +echoandexit 0 '' 'hello stderr from echoandexit' +! stdout .+ +stderr 'hello stderr from echoandexit' + +# Verify that we do update stderr when we attempt to write via Stdout() or Stderr() +fprintargs stderr hello stderr from fprintargs +! stdout .+ +stderr 'hello stderr from fprintargs' +echoandexit 0 'hello stdout from echoandexit' +stdout 'hello stdout from echoandexit' +! stderr .+ +fprintargs stdout hello stdout from fprintargs +stdout 'hello stdout from fprintargs' +! stderr .+ +echoandexit 0 '' 'hello stderr from echoandexit' +! stdout .+ +stderr 'hello stderr from echoandexit' diff --git a/testscript/testdata/long_diff.txt b/testscript/testdata/long_diff.txt index de9f1be7..ea42157c 100644 --- a/testscript/testdata/long_diff.txt +++ b/testscript/testdata/long_diff.txt @@ -111,17 +111,14 @@ cmpenv stdout stdout.golden >a -- stdout.golden -- > cmp a b +diff a b --- a +++ b -@@ -1,3 +1,4 @@ +@@ -1,4 +1,4 @@ +-a +b a a a -@@ -46,4 +47,3 @@ - a - a - a --a FAIL: $$WORK${/}dir${/}script.txt:1: a and b differ diff --git a/testscript/testdata/pty.txt b/testscript/testdata/pty.txt new file mode 100644 index 00000000..1fb0cfbf --- /dev/null +++ b/testscript/testdata/pty.txt @@ -0,0 +1,11 @@ +[!linux] [!darwin] skip +[darwin] skip # https://go.dev/issue/61779 + +ttyin secretwords.txt +terminalprompt +ttyout 'magic words' +! stderr . +! stdout . + +-- secretwords.txt -- +SQUEAMISHOSSIFRAGE diff --git a/testscript/testdata/testscript_duplicate_name.txt b/testscript/testdata/testscript_duplicate_name.txt new file mode 100644 index 00000000..b8fd4c6d --- /dev/null +++ b/testscript/testdata/testscript_duplicate_name.txt @@ -0,0 +1,12 @@ +# Check that RequireUniqueNames works; +# it should reject txtar archives with duplicate names as defined by the host system. + +unquote scripts-normalized/testscript.txt + +testscript scripts-normalized +! testscript -unique-names scripts-normalized +stdout '.* would overwrite .* \(because RequireUniqueNames is enabled\)' + +-- scripts-normalized/testscript.txt -- +>-- file -- +>-- dir/../file -- \ No newline at end of file diff --git a/testscript/testdata/testscript_logging.txt b/testscript/testdata/testscript_logging.txt new file mode 100644 index 00000000..60975fee --- /dev/null +++ b/testscript/testdata/testscript_logging.txt @@ -0,0 +1,108 @@ +# non-verbose, non-continue +! testscript scripts +cmpenv stdout expect-stdout.txt + +# verbose +! testscript -v scripts +cmpenv stdout expect-stdout-v.txt + +# continue +! testscript -continue scripts +cmpenv stdout expect-stdout-c.txt + +# verbose, continue +! testscript -v -continue scripts +cmpenv stdout expect-stdout-vc.txt + +-- scripts/testscript.txt -- +# comment 1 +printargs section1 + +# comment 2 +printargs section2 + +# comment 3 +printargs section3 +status 1 + +# comment 4 +printargs section3 + +# comment 5 +printargs section5 +status 1 + +-- expect-stdout.txt -- +# comment 1 (0.000s) +# comment 2 (0.000s) +# comment 3 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure +-- expect-stdout-v.txt -- +# comment 1 (0.000s) +> printargs section1 +[stdout] +["printargs" "section1"] +# comment 2 (0.000s) +> printargs section2 +[stdout] +["printargs" "section2"] +# comment 3 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure +-- expect-stdout-c.txt -- +# comment 1 (0.000s) +# comment 2 (0.000s) +# comment 3 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure +# comment 4 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +# comment 5 (0.000s) +> printargs section5 +[stdout] +["printargs" "section5"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:16: unexpected command failure +-- expect-stdout-vc.txt -- +# comment 1 (0.000s) +> printargs section1 +[stdout] +["printargs" "section1"] +# comment 2 (0.000s) +> printargs section2 +[stdout] +["printargs" "section2"] +# comment 3 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:9: unexpected command failure +# comment 4 (0.000s) +> printargs section3 +[stdout] +["printargs" "section3"] +# comment 5 (0.000s) +> printargs section5 +[stdout] +["printargs" "section5"] +> status 1 +[exit status 1] +FAIL: $$WORK${/}scripts${/}testscript.txt:16: unexpected command failure diff --git a/testscript/testdata/testscript_notfound.txt b/testscript/testdata/testscript_notfound.txt new file mode 100644 index 00000000..4248e681 --- /dev/null +++ b/testscript/testdata/testscript_notfound.txt @@ -0,0 +1,17 @@ +# Check that unknown commands output a useful error message + +! testscript notfound +stdout 'unknown command "notexist"' + +! testscript negation +stdout 'unknown command "!exists" \(did you mean "! exists"\?\)' + +! testscript misspelled +stdout 'unknown command "exits" \(did you mean "exists"\?\)' + +-- notfound/script.txt -- +notexist +-- negation/script.txt -- +!exists file +-- misspelled/script.txt -- +exits file diff --git a/testscript/testdata/testscript_stdout_stderr_error.txt b/testscript/testdata/testscript_stdout_stderr_error.txt new file mode 100644 index 00000000..a69c7d01 --- /dev/null +++ b/testscript/testdata/testscript_stdout_stderr_error.txt @@ -0,0 +1,20 @@ +# Verify that stdout and stderr get set event when a user-builtin +# command aborts. Note that we need to assert against stdout +# because our meta testscript command sees only a single log. +unquote scripts/testscript.txt +! testscript -v scripts +cmpenv stdout stdout.golden + +-- scripts/testscript.txt -- +> printargs hello world +> echoandexit 1 'this is stdout' 'this is stderr' +-- stdout.golden -- +> printargs hello world +[stdout] +["printargs" "hello" "world"] +> echoandexit 1 'this is stdout' 'this is stderr' +[stdout] +this is stdout +[stderr] +this is stderr +FAIL: ${$}WORK${/}scripts${/}testscript.txt:2: told to exit with code 1 diff --git a/testscript/testscript.go b/testscript/testscript.go index 2b0cd75e..089e1038 100644 --- a/testscript/testscript.go +++ b/testscript/testscript.go @@ -10,24 +10,30 @@ package testscript import ( "bytes" "context" + "errors" "flag" "fmt" "go/build" - "io/ioutil" + "io" + "io/fs" "os" "os/exec" "path/filepath" "regexp" "runtime" + "sort" "strings" "sync/atomic" + "syscall" "testing" "time" "github.com/rogpeppe/go-internal/imports" + "github.com/rogpeppe/go-internal/internal/misspell" "github.com/rogpeppe/go-internal/internal/os/execpath" "github.com/rogpeppe/go-internal/par" "github.com/rogpeppe/go-internal/testenv" + "github.com/rogpeppe/go-internal/testscript/internal/pty" "github.com/rogpeppe/go-internal/txtar" ) @@ -40,6 +46,16 @@ var execCache par.Cache // poke at the test file tree afterward. var testWork = flag.Bool("testwork", false, "") +// timeSince is defined as a variable so that it can be overridden +// for the local testscript tests so that we can test against predictable +// output. +var timeSince = time.Since + +// showVerboseEnv specifies whether the environment should be displayed +// automatically when in verbose mode. This is set to false for the local testscript tests so we +// can test against predictable output. +var showVerboseEnv = true + // Env holds the environment to use at the start of a test script invocation. type Env struct { // WorkDir holds the path to the root directory of the @@ -84,6 +100,13 @@ func (e *Env) Getenv(key string) string { return "" } +func envvarname(k string) string { + if runtime.GOOS == "windows" { + return strings.ToLower(k) + } + return k +} + // Setenv sets the value of the environment variable named by the key. It // panics if key is invalid. func (e *Env) Setenv(key, value string) { @@ -139,11 +162,7 @@ type Params struct { // $GOTMPDIR/go-test-script*, where $GOTMPDIR defaults to os.TempDir(). WorkdirRoot string - // IgnoreMissedCoverage specifies that if coverage information - // is being generated (with the -test.coverprofile flag) and a subcommand - // function passed to RunMain fails to generate coverage information - // (for example because the function invoked os.Exit), then the - // error will be ignored. + // Deprecated: this option is no longer used. IgnoreMissedCoverage bool // UpdateScripts specifies that if a `cmp` command fails and its second @@ -161,11 +180,28 @@ type Params struct { // consistency across test scripts as well as keep separate process // executions explicit. RequireExplicitExec bool + + // RequireUniqueNames requires that names in the txtar archive are unique. + // By default, later entries silently overwrite earlier ones. + RequireUniqueNames bool + + // ContinueOnError causes a testscript to try to continue in + // the face of errors. Once an error has occurred, the script + // will continue as if in verbose mode. + ContinueOnError bool + + // Deadline, if not zero, specifies the time at which the test run will have + // exceeded the timeout. It is equivalent to testing.T's Deadline method, + // and Run will set it to the method's return value if this field is zero. + Deadline time.Time } // RunDir runs the tests in the given directory. All files in dir with a ".txt" // or ".txtar" extension are considered to be test files. func Run(t *testing.T, p Params) { + if deadline, ok := t.Deadline(); ok && p.Deadline.IsZero() { + p.Deadline = deadline + } RunT(tshim{t}, p) } @@ -225,14 +261,14 @@ func RunT(t T, p Params) { } testTempDir := p.WorkdirRoot if testTempDir == "" { - testTempDir, err = ioutil.TempDir(os.Getenv("GOTMPDIR"), "go-test-script") + testTempDir, err = os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-test-script") if err != nil { t.Fatal(err) } } else { p.TestWork = true } - // The temp dir returned by ioutil.TempDir might be a sym linked dir (default + // The temp dir returned by os.MkdirTemp might be a sym linked dir (default // behaviour in macOS). That could mess up matching that includes $WORK if, // for example, an external program outputs resolved paths. Evaluating the // dir here will ensure consistency. @@ -240,6 +276,37 @@ func RunT(t T, p Params) { if err != nil { t.Fatal(err) } + + var ( + ctx = context.Background() + gracePeriod = 100 * time.Millisecond + cancel context.CancelFunc + ) + if !p.Deadline.IsZero() { + timeout := time.Until(p.Deadline) + + // If time allows, increase the termination grace period to 5% of the + // remaining time. + if gp := timeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up. We will send the first termination signal when + // the context expires, then wait one grace period for the process to + // produce whatever useful output it can (such as a stack trace). After the + // first grace period expires, we'll escalate to os.Kill, leaving the second + // grace period for the test function to record its output before the test + // process itself terminates. + timeout -= 2 * gracePeriod + + ctx, cancel = context.WithTimeout(ctx, timeout) + // We don't defer cancel() because RunT returns before the sub-tests, + // and we don't have access to Cleanup due to the T interface. Instead, + // we call it after the refCount goes to zero below. + _ = cancel + } + refCount := int32(len(files)) for _, file := range files { file := file @@ -253,7 +320,8 @@ func RunT(t T, p Params) { name: name, file: file, params: p, - ctxt: context.Background(), + ctxt: ctx, + gracePeriod: gracePeriod, deferred: func() {}, scriptFiles: make(map[string]string), scriptUpdates: make(map[string]string), @@ -265,8 +333,11 @@ func RunT(t T, p Params) { removeAll(ts.workdir) if atomic.AddInt32(&refCount, -1) == 0 { // This is the last subtest to finish. Remove the - // parent directory too. + // parent directory too, and cancel the context. os.Remove(testTempDir) + if cancel != nil { + cancel() + } } }() ts.run() @@ -293,6 +364,9 @@ type TestScript struct { stdin string // standard input to next 'go' command; set by 'stdin' command. stdout string // standard output from last 'go' command; for 'stdout' command stderr string // standard error from last 'go' command; for 'stderr' command + ttyin string // terminal input; set by 'ttyin' command + stdinPty bool // connect pty to standard input; set by 'ttyin -stdin' command + ttyout string // terminal output; for 'ttyout' command stopped bool // test wants to stop early start time.Time // time phase started background []backgroundCmd // backgrounded 'exec' and 'go' commands @@ -301,7 +375,19 @@ type TestScript struct { scriptFiles map[string]string // files stored in the txtar archive (absolute paths -> path in script) scriptUpdates map[string]string // updates to testscript files via UpdateScripts. - ctxt context.Context // per TestScript context + // runningBuiltin indicates if we are running a user-supplied builtin + // command. These commands are specified via Params.Cmds. + runningBuiltin bool + + // builtinStd(out|err) are established if a user-supplied builtin command + // requests Stdout() or Stderr(). Either both are non-nil, or both are nil. + // This invariant is maintained by both setBuiltinStd() and + // clearBuiltinStd(). + builtinStdout *strings.Builder + builtinStderr *strings.Builder + + ctxt context.Context // per TestScript context + gracePeriod time.Duration // time between SIGQUIT and SIGKILL } type backgroundCmd struct { @@ -311,9 +397,33 @@ type backgroundCmd struct { neg bool // if true, cmd should fail } +func writeFile(name string, data []byte, perm fs.FileMode, excl bool) error { + oflags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC + if excl { + oflags |= os.O_EXCL + } + f, err := os.OpenFile(name, oflags, perm) + if err != nil { + return err + } + defer f.Close() + if _, err := f.Write(data); err != nil { + return fmt.Errorf("cannot write file contents: %v", err) + } + return nil +} + +// Name returns the short name or basename of the test script. +func (ts *TestScript) Name() string { return ts.name } + // setup sets up the test execution temporary directory and environment. // It returns the comment section of the txtar archive. func (ts *TestScript) setup() string { + defer catchFailNow(func() { + // There's been a failure in setup; fail immediately regardless + // of the ContinueOnError flag. + ts.t.FailNow() + }) ts.workdir = filepath.Join(ts.testTempDir, "script-"+ts.name) // Establish a temporary directory in workdir, but use a prefix that ensures @@ -330,22 +440,34 @@ func (ts *TestScript) setup() string { Vars: []string{ "WORK=" + ts.workdir, // must be first for ts.abbrev "PATH=" + os.Getenv("PATH"), + "GOTRACEBACK=system", homeEnvName() + "=/no-home", tempEnvName() + "=" + tmpDir, "devnull=" + os.DevNull, "/=" + string(os.PathSeparator), ":=" + string(os.PathListSeparator), "$=$", - - // If we are collecting coverage profiles for merging into the main one, - // ensure the environment variable is forwarded to sub-processes. - "TESTSCRIPT_COVER_DIR=" + os.Getenv("TESTSCRIPT_COVER_DIR"), }, WorkDir: ts.workdir, Values: make(map[interface{}]interface{}), Cd: ts.workdir, ts: ts, } + + // These env vars affect how a Go program behaves at run-time; + // If the user or `go test` wrapper set them, we should propagate them + // so that sub-process commands run via the test binary see them as well. + for _, name := range []string{ + // If we are collecting coverage profiles, e.g. `go test -coverprofile`. + "GOCOVERDIR", + // If the user set GORACE when running a command like `go test -race`, + // such as GORACE=atexit_sleep_ms=10 to avoid the default 1s sleeps. + "GORACE", + } { + if val := os.Getenv(name); val != "" { + env.Vars = append(env.Vars, name+"="+val) + } + } // Must preserve SYSTEMROOT on Windows: https://github.com/golang/go/issues/25513 et al if runtime.GOOS == "windows" { env.Vars = append(env.Vars, @@ -366,7 +488,12 @@ func (ts *TestScript) setup() string { name := ts.MkAbs(ts.expand(f.Name)) ts.scriptFiles[name] = f.Name ts.Check(os.MkdirAll(filepath.Dir(name), 0o777)) - ts.Check(ioutil.WriteFile(name, f.Data, 0o666)) + switch err := writeFile(name, f.Data, 0o666, ts.params.RequireUniqueNames); { + case ts.params.RequireUniqueNames && errors.Is(err, fs.ErrExist): + ts.Check(fmt.Errorf("%s would overwrite %s (because RequireUniqueNames is enabled)", f.Name, name)) + default: + ts.Check(err) + } } // Run any user-defined setup. if ts.params.Setup != nil { @@ -389,8 +516,9 @@ func (ts *TestScript) setup() string { func (ts *TestScript) run() { // Truncate log at end of last phase marker, // discarding details of successful phase. + verbose := ts.t.Verbose() rewind := func() { - if !ts.t.Verbose() { + if !verbose { ts.log.Truncate(ts.mark) } } @@ -400,12 +528,13 @@ func (ts *TestScript) run() { if ts.mark > 0 && !ts.start.IsZero() { afterMark := append([]byte{}, ts.log.Bytes()[ts.mark:]...) ts.log.Truncate(ts.mark - 1) // cut \n and afterMark - fmt.Fprintf(&ts.log, " (%.3fs)\n", time.Since(ts.start).Seconds()) + fmt.Fprintf(&ts.log, " (%.3fs)\n", timeSince(ts.start).Seconds()) ts.log.Write(afterMark) } ts.start = time.Time{} } + failed := false defer func() { // On a normal exit from the test loop, background processes are cleaned up // before we print PASS. If we return early (e.g., due to a test failure), @@ -413,7 +542,7 @@ func (ts *TestScript) run() { for _, bg := range ts.background { interruptProcess(bg.cmd.Process) } - if ts.t.Verbose() || hasFailed(ts.t) { + if ts.t.Verbose() || failed { // In verbose mode or on test failure, we want to see what happened in the background // processes too. ts.waitBackground(false) @@ -434,7 +563,7 @@ func (ts *TestScript) run() { script := ts.setup() // With -v or -testwork, start log with full environment. - if *testWork || ts.t.Verbose() { + if *testWork || (showVerboseEnv && ts.t.Verbose()) { // Display environment. ts.cmdEnv(false, nil) fmt.Fprintf(&ts.log, "\n") @@ -444,7 +573,6 @@ func (ts *TestScript) run() { // Run script. // See testdata/script/README for documentation of script form. -Script: for script != "" { // Extract next line. ts.lineno++ @@ -458,7 +586,9 @@ Script: // # is a comment indicating the start of new phase. if strings.HasPrefix(line, "#") { // If there was a previous phase, it succeeded, - // so rewind the log to delete its details (unless -v is in use). + // so rewind the log to delete its details (unless -v is in use or + // ContinueOnError was enabled and there was a previous error, + // causing verbose to be set to true). // If nothing has happened at all since the mark, // rewinding is a no-op and adding elapsed time // for doing nothing is meaningless, so don't. @@ -473,60 +603,16 @@ Script: continue } - // Parse input line. Ignore blanks entirely. - args := ts.parse(line) - if len(args) == 0 { - continue - } - - // Echo command to log. - fmt.Fprintf(&ts.log, "> %s\n", line) - - // Command prefix [cond] means only run this command if cond is satisfied. - for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") { - cond := args[0] - cond = cond[1 : len(cond)-1] - cond = strings.TrimSpace(cond) - args = args[1:] - if len(args) == 0 { - ts.Fatalf("missing command after condition") - } - want := true - if strings.HasPrefix(cond, "!") { - want = false - cond = strings.TrimSpace(cond[1:]) - } - ok, err := ts.condition(cond) - if err != nil { - ts.Fatalf("bad condition %q: %v", cond, err) - } - if ok != want { - // Don't run rest of line. - continue Script - } - } - - // Command prefix ! means negate the expectations about this command: - // go command should fail, match should not be found, etc. - neg := false - if args[0] == "!" { - neg = true - args = args[1:] - if len(args) == 0 { - ts.Fatalf("! on line by itself") + ok := ts.runLine(line) + if !ok { + failed = true + if ts.params.ContinueOnError { + verbose = true + } else { + ts.t.FailNow() } } - // Run command. - cmd := scriptCmds[args[0]] - if cmd == nil { - cmd = ts.params.Cmds[args[0]] - } - if cmd == nil { - ts.Fatalf("unknown command %q", args[0]) - } - cmd(ts, neg, args[1:]) - // Command can ask script to stop early. if ts.stopped { // Break instead of returning, so that we check the status of any @@ -540,6 +626,12 @@ Script: } ts.cmdWait(false, nil) + // If we reached here but we've failed (probably because ContinueOnError + // was set), don't wipe the log and print "PASS". + if failed { + ts.t.FailNow() + } + // Final phase ended. rewind() markTime() @@ -548,11 +640,129 @@ Script: } } -func hasFailed(t T) bool { - if t, ok := t.(TFailed); ok { - return t.Failed() +func (ts *TestScript) runLine(line string) (runOK bool) { + defer catchFailNow(func() { + runOK = false + }) + + // Parse input line. Ignore blanks entirely. + args := ts.parse(line) + if len(args) == 0 { + return true } - return false + + // Echo command to log. + fmt.Fprintf(&ts.log, "> %s\n", line) + + // Command prefix [cond] means only run this command if cond is satisfied. + for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") { + cond := args[0] + cond = cond[1 : len(cond)-1] + cond = strings.TrimSpace(cond) + args = args[1:] + if len(args) == 0 { + ts.Fatalf("missing command after condition") + } + want := true + if strings.HasPrefix(cond, "!") { + want = false + cond = strings.TrimSpace(cond[1:]) + } + ok, err := ts.condition(cond) + if err != nil { + ts.Fatalf("bad condition %q: %v", cond, err) + } + if ok != want { + // Don't run rest of line. + return true + } + } + + // Command prefix ! means negate the expectations about this command: + // go command should fail, match should not be found, etc. + neg := false + if args[0] == "!" { + neg = true + args = args[1:] + if len(args) == 0 { + ts.Fatalf("! on line by itself") + } + } + + // Run command. + cmd := scriptCmds[args[0]] + if cmd == nil { + cmd = ts.params.Cmds[args[0]] + } + if cmd == nil { + // try to find spelling corrections. We arbitrarily limit the number of + // corrections, to not be too noisy. + switch c := ts.cmdSuggestions(args[0]); len(c) { + case 1: + ts.Fatalf("unknown command %q (did you mean %q?)", args[0], c[0]) + case 2, 3, 4: + ts.Fatalf("unknown command %q (did you mean one of %q?)", args[0], c) + default: + ts.Fatalf("unknown command %q", args[0]) + } + } + ts.callBuiltinCmd(args[0], func() { + cmd(ts, neg, args[1:]) + }) + return true +} + +func (ts *TestScript) callBuiltinCmd(cmd string, runCmd func()) { + ts.runningBuiltin = true + defer func() { + r := recover() + ts.runningBuiltin = false + ts.clearBuiltinStd() + switch r { + case nil: + // we did not panic + default: + // re-"throw" the panic + panic(r) + } + }() + runCmd() +} + +func (ts *TestScript) cmdSuggestions(name string) []string { + // special case: spell-correct `!cmd` to `! cmd` + if strings.HasPrefix(name, "!") { + if _, ok := scriptCmds[name[1:]]; ok { + return []string{"! " + name[1:]} + } + if _, ok := ts.params.Cmds[name[1:]]; ok { + return []string{"! " + name[1:]} + } + } + var candidates []string + for c := range scriptCmds { + if misspell.AlmostEqual(name, c) { + candidates = append(candidates, c) + } + } + for c := range ts.params.Cmds { + if misspell.AlmostEqual(name, c) { + candidates = append(candidates, c) + } + } + if len(candidates) == 0 { + return nil + } + // deduplicate candidates + // TODO: Use slices.Compact (and maybe slices.Sort) once we can use Go 1.21 + sort.Strings(candidates) + out := candidates[:1] + for _, c := range candidates[1:] { + if out[len(out)-1] == c { + out = append(out, c) + } + } + return out } func (ts *TestScript) applyScriptUpdates() { @@ -570,7 +780,7 @@ func (ts *TestScript) applyScriptUpdates() { if txtar.NeedsQuote(data) { data1, err := txtar.Quote(data) if err != nil { - ts.t.Fatal(fmt.Sprintf("cannot update script file %q: %v", f.Name, err)) + ts.Fatalf("cannot update script file %q: %v", f.Name, err) continue } data = data1 @@ -583,12 +793,27 @@ func (ts *TestScript) applyScriptUpdates() { panic("script update file not found") } } - if err := ioutil.WriteFile(ts.file, txtar.Format(ts.archive), 0o666); err != nil { + if err := os.WriteFile(ts.file, txtar.Format(ts.archive), 0o666); err != nil { ts.t.Fatal("cannot update script: ", err) } ts.Logf("%s updated", ts.file) } +var failNow = errors.New("fail now!") + +// catchFailNow catches any panic from Fatalf and calls +// f if it did so. It must be called in a defer. +func catchFailNow(f func()) { + e := recover() + if e == nil { + return + } + if e != failNow { + panic(e) + } + f() +} + // condition reports whether the given condition is satisfied. func (ts *TestScript) condition(cond string) (bool, error) { switch { @@ -665,6 +890,60 @@ func (ts *TestScript) Check(err error) { } } +// Stdout returns an io.Writer that can be used by a user-supplied builtin +// command (delcared via Params.Cmds) to write to stdout. If this method is +// called outside of the execution of a user-supplied builtin command, the +// call panics. +func (ts *TestScript) Stdout() io.Writer { + if !ts.runningBuiltin { + panic("can only call TestScript.Stdout when running a builtin command") + } + ts.setBuiltinStd() + return ts.builtinStdout +} + +// Stderr returns an io.Writer that can be used by a user-supplied builtin +// command (delcared via Params.Cmds) to write to stderr. If this method is +// called outside of the execution of a user-supplied builtin command, the +// call panics. +func (ts *TestScript) Stderr() io.Writer { + if !ts.runningBuiltin { + panic("can only call TestScript.Stderr when running a builtin command") + } + ts.setBuiltinStd() + return ts.builtinStderr +} + +// setBuiltinStd ensures that builtinStdout and builtinStderr are non nil. +func (ts *TestScript) setBuiltinStd() { + // This method must maintain the invariant that both builtinStdout and + // builtinStderr are set or neither are set + + // If both are set, nothing to do + if ts.builtinStdout != nil && ts.builtinStderr != nil { + return + } + ts.builtinStdout = new(strings.Builder) + ts.builtinStderr = new(strings.Builder) +} + +// clearBuiltinStd sets ts.stdout and ts.stderr from the builtin command +// buffers, logs both, and resets both builtinStdout and builtinStderr to nil. +func (ts *TestScript) clearBuiltinStd() { + // This method must maintain the invariant that both builtinStdout and + // builtinStderr are set or neither are set + + // If neither set, nothing to do + if ts.builtinStdout == nil && ts.builtinStderr == nil { + return + } + ts.stdout = ts.builtinStdout.String() + ts.builtinStdout = nil + ts.stderr = ts.builtinStderr.String() + ts.builtinStderr = nil + ts.logStd() +} + // Logf appends the given formatted message to the test log transcript. func (ts *TestScript) Logf(format string, args ...interface{}) { format = strings.TrimSuffix(format, "\n") @@ -685,16 +964,49 @@ func (ts *TestScript) exec(command string, args ...string) (stdout, stderr strin var stdoutBuf, stderrBuf strings.Builder cmd.Stdout = &stdoutBuf cmd.Stderr = &stderrBuf + if ts.ttyin != "" { + ctrl, tty, err := pty.Open() + if err != nil { + return "", "", err + } + doneR, doneW := make(chan struct{}), make(chan struct{}) + var ptyBuf strings.Builder + go func() { + io.Copy(ctrl, strings.NewReader(ts.ttyin)) + ctrl.Write([]byte{4 /* EOT */}) + close(doneW) + }() + go func() { + io.Copy(&ptyBuf, ctrl) + close(doneR) + }() + defer func() { + tty.Close() + ctrl.Close() + <-doneR + <-doneW + ts.ttyin = "" + ts.ttyout = ptyBuf.String() + }() + pty.SetCtty(cmd, tty) + if ts.stdinPty { + cmd.Stdin = tty + } + } if err = cmd.Start(); err == nil { - err = ctxWait(ts.ctxt, cmd) + err = waitOrStop(ts.ctxt, cmd, ts.gracePeriod) } ts.stdin = "" + ts.stdinPty = false return stdoutBuf.String(), stderrBuf.String(), err } // execBackground starts the given command line (an actual subprocess, not simulated) // in ts.cd with environment ts.env. func (ts *TestScript) execBackground(command string, args ...string) (*exec.Cmd, error) { + if ts.ttyin != "" { + return nil, errors.New("ttyin is not supported by background commands") + } cmd, err := ts.buildExecCmd(command, args...) if err != nil { return nil, err @@ -731,21 +1043,68 @@ func (ts *TestScript) BackgroundCmds() []*exec.Cmd { return cmds } -// ctxWait is like cmd.Wait, but terminates cmd with os.Interrupt if ctx becomes done. +// waitOrStop waits for the already-started command cmd by calling its Wait method. // -// This differs from exec.CommandContext in that it prefers os.Interrupt over os.Kill. -// (See https://golang.org/issue/21135.) -func ctxWait(ctx context.Context, cmd *exec.Cmd) error { - errc := make(chan error, 1) - go func() { errc <- cmd.Wait() }() - - select { - case err := <-errc: - return err - case <-ctx.Done(): - interruptProcess(cmd.Process) - return <-errc +// If cmd does not return before ctx is done, waitOrStop sends it an interrupt +// signal. If killDelay is positive, waitOrStop waits that additional period for +// Wait to return before sending os.Kill. +func waitOrStop(ctx context.Context, cmd *exec.Cmd, killDelay time.Duration) error { + if cmd.Process == nil { + panic("waitOrStop called with a nil cmd.Process — missing Start call?") + } + + errc := make(chan error) + go func() { + select { + case errc <- nil: + return + case <-ctx.Done(): + } + + var interrupt os.Signal = syscall.SIGQUIT + if runtime.GOOS == "windows" { + // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on + // Windows; using it with os.Process.Signal will return an error.” + // Fall back directly to Kill instead. + interrupt = os.Kill + } + + err := cmd.Process.Signal(interrupt) + if err == nil { + err = ctx.Err() // Report ctx.Err() as the reason we interrupted. + } else if err == os.ErrProcessDone { + errc <- nil + return + } + + if killDelay > 0 { + timer := time.NewTimer(killDelay) + select { + // Report ctx.Err() as the reason we interrupted the process... + case errc <- ctx.Err(): + timer.Stop() + return + // ...but after killDelay has elapsed, fall back to a stronger signal. + case <-timer.C: + } + + // Wait still hasn't returned. + // Kill the process harder to make sure that it exits. + // + // Ignore any error: if cmd.Process has already terminated, we still + // want to send ctx.Err() (or the error from the Interrupt call) + // to properly attribute the signal that may have terminated it. + _ = cmd.Process.Kill() + } + + errc <- err + }() + + waitErr := cmd.Wait() + if interruptErr := <-errc; interruptErr != nil { + return interruptErr } + return waitErr } // interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise. @@ -763,13 +1122,18 @@ func interruptProcess(p *os.Process) { func (ts *TestScript) Exec(command string, args ...string) error { var err error ts.stdout, ts.stderr, err = ts.exec(command, args...) + ts.logStd() + return err +} + +// logStd logs the current non-empty values of stdout and stderr. +func (ts *TestScript) logStd() { if ts.stdout != "" { ts.Logf("[stdout]\n%s", ts.stdout) } if ts.stderr != "" { ts.Logf("[stderr]\n%s", ts.stderr) } - return err } // expand applies environment variable expansion to the string s. @@ -784,8 +1148,17 @@ func (ts *TestScript) expand(s string) string { // fatalf aborts the test with the given failure message. func (ts *TestScript) Fatalf(format string, args ...interface{}) { + // In user-supplied builtins, the only way we have of aborting + // is via Fatalf. Hence if we are aborting from a user-supplied + // builtin, it's important we first log stdout and stderr. If + // we are not, the following call is a no-op. + ts.clearBuiltinStd() + fmt.Fprintf(&ts.log, "FAIL: %s:%d: %s\n", ts.file, ts.lineno, fmt.Sprintf(format, args...)) - ts.t.FailNow() + // This should be caught by the defer inside the TestScript.runLine method. + // We do this rather than calling ts.t.FailNow directly because we want to + // be able to continue on error when Params.ContinueOnError is set. + panic(failNow) } // MkAbs interprets file relative to the test script's current directory @@ -810,9 +1183,11 @@ func (ts *TestScript) ReadFile(file string) string { return ts.stdout case "stderr": return ts.stderr + case "ttyout": + return ts.ttyout default: file = ts.MkAbs(file) - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) ts.Check(err) return string(data) } @@ -893,11 +1268,11 @@ func (ts *TestScript) parse(line string) []string { func removeAll(dir string) error { // module cache has 0o444 directories; // make them writable in order to remove content. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { if err != nil { return nil // ignore errors walking in file system } - if info.IsDir() { + if entry.IsDir() { os.Chmod(path, 0o777) } return nil diff --git a/testscript/testscript_test.go b/testscript/testscript_test.go index c09cabb8..2b4c6d32 100644 --- a/testscript/testscript_test.go +++ b/testscript/testscript_test.go @@ -58,12 +58,34 @@ func signalCatcher() int { return 0 } +func terminalPrompt() int { + tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + fmt.Println(err) + return 1 + } + tty.WriteString("The magic words are: ") + var words string + fmt.Fscanln(tty, &words) + if words != "SQUEAMISHOSSIFRAGE" { + fmt.Println(words) + return 42 + } + return 0 +} + func TestMain(m *testing.M) { + timeSince = func(t time.Time) time.Duration { + return 0 + } + + showVerboseEnv = false os.Exit(RunMain(m, map[string]func() int{ - "printargs": printArgs, - "fprintargs": fprintArgs, - "status": exitWithStatus, - "signalcatcher": signalCatcher, + "printargs": printArgs, + "fprintargs": fprintArgs, + "status": exitWithStatus, + "signalcatcher": signalCatcher, + "terminalprompt": terminalPrompt, })) } @@ -130,6 +152,31 @@ func TestEnv(t *testing.T) { } } +func TestSetupFailure(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "foo.txt"), nil, 0o666); err != nil { + t.Fatal(err) + } + ft := &fakeT{} + func() { + defer catchAbort() + RunT(ft, Params{ + Dir: dir, + Setup: func(*Env) error { + return fmt.Errorf("some failure") + }, + }) + }() + if !ft.failed { + t.Fatal("test should have failed because of setup failure") + } + + want := regexp.MustCompile(`^FAIL: .*: some failure\n$`) + if got := ft.log.String(); !want.MatchString(got) { + t.Fatalf("expected msg to match `%v`; got:\n%q", want, got) + } +} + func TestScripts(t *testing.T) { // TODO set temp directory. testDeferCount := 0 @@ -183,44 +230,46 @@ func TestScripts(t *testing.T) { fset := flag.NewFlagSet("testscript", flag.ContinueOnError) fUpdate := fset.Bool("update", false, "update scripts when cmp fails") fExplicitExec := fset.Bool("explicit-exec", false, "require explicit use of exec for commands") - fVerbose := fset.Bool("verbose", false, "be verbose with output") + fUniqueNames := fset.Bool("unique-names", false, "require unique names in txtar archive") + fVerbose := fset.Bool("v", false, "be verbose with output") + fContinue := fset.Bool("continue", false, "continue on error") if err := fset.Parse(args); err != nil { ts.Fatalf("failed to parse args for testscript: %v", err) } if fset.NArg() != 1 { - ts.Fatalf("testscript [-verbose] [-update] [-explicit-exec] ") + ts.Fatalf("testscript [-v] [-continue] [-update] [-explicit-exec] ") } dir := fset.Arg(0) - t := &fakeT{ts: ts, verbose: *fVerbose} + t := &fakeT{verbose: *fVerbose} func() { - defer func() { - if err := recover(); err != nil { - if err != errAbort { - panic(err) - } - } - }() + defer catchAbort() RunT(t, Params{ Dir: ts.MkAbs(dir), UpdateScripts: *fUpdate, RequireExplicitExec: *fExplicitExec, + RequireUniqueNames: *fUniqueNames, Cmds: map[string]func(ts *TestScript, neg bool, args []string){ "some-param-cmd": func(ts *TestScript, neg bool, args []string) { }, + "echoandexit": echoandexit, }, + ContinueOnError: *fContinue, }) }() - ts.stdout = strings.Replace(t.log.String(), ts.workdir, "$WORK", -1) + stdout := t.log.String() + stdout = strings.ReplaceAll(stdout, ts.workdir, "$WORK") + fmt.Fprint(ts.Stdout(), stdout) if neg { - if len(t.failMsgs) == 0 { + if !t.failed { ts.Fatalf("testscript unexpectedly succeeded") } return } - if len(t.failMsgs) > 0 { - ts.Fatalf("testscript unexpectedly failed with errors: %q", t.failMsgs) + if t.failed { + ts.Fatalf("testscript unexpectedly failed with errors: %q", &t.log) } }, + "echoandexit": echoandexit, }, Setup: func(env *Env) error { infos, err := ioutil.ReadDir(env.WorkDir) @@ -246,6 +295,33 @@ func TestScripts(t *testing.T) { // TODO check that the temp directory has been removed. } +func echoandexit(ts *TestScript, neg bool, args []string) { + // Takes at least one argument + // + // args[0] - int that indicates the exit code of the command + // args[1] - the string to echo to stdout if non-empty + // args[2] - the string to echo to stderr if non-empty + if len(args) == 0 || len(args) > 3 { + ts.Fatalf("echoandexit takes at least one and at most three arguments") + } + if neg { + ts.Fatalf("neg means nothing for echoandexit") + } + exitCode, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + ts.Fatalf("failed to parse exit code from %q: %v", args[0], err) + } + if len(args) > 1 && args[1] != "" { + fmt.Fprint(ts.Stdout(), args[1]) + } + if len(args) > 2 && args[2] != "" { + fmt.Fprint(ts.Stderr(), args[2]) + } + if exitCode != 0 { + ts.Fatalf("told to exit with code %d", exitCode) + } +} + // TestTestwork tests that using the flag -testwork will make sure the work dir isn't removed // after the test is done. It uses an empty testscript file that doesn't do anything. func TestTestwork(t *testing.T) { @@ -303,24 +379,21 @@ func TestWorkdirRoot(t *testing.T) { func TestBadDir(t *testing.T) { ft := new(fakeT) func() { - defer func() { - if err := recover(); err != nil { - if err != errAbort { - panic(err) - } - } - }() + defer catchAbort() RunT(ft, Params{ Dir: "thiswillnevermatch", }) }() - wantCount := 1 - if got := len(ft.failMsgs); got != wantCount { - t.Fatalf("expected %v fail message; got %v", wantCount, got) + want := regexp.MustCompile(`no txtar nor txt scripts found in dir thiswillnevermatch`) + if got := ft.log.String(); !want.MatchString(got) { + t.Fatalf("expected msg to match `%v`; got:\n%v", want, got) } - wantMsg := regexp.MustCompile(`no txtar nor txt scripts found in dir thiswillnevermatch`) - if got := ft.failMsgs[0]; !wantMsg.MatchString(got) { - t.Fatalf("expected msg to match `%v`; got:\n%v", wantMsg, got) +} + +// catchAbort catches the panic raised by fakeT.FailNow. +func catchAbort() { + if err := recover(); err != nil && err != errAbort { + panic(err) } } @@ -391,11 +464,9 @@ func waitFile(ts *TestScript, neg bool, args []string) { } type fakeT struct { - ts *TestScript - log bytes.Buffer - failMsgs []string - verbose bool - failed bool + log strings.Builder + verbose bool + failed bool } var errAbort = errors.New("abort test") @@ -405,9 +476,8 @@ func (t *fakeT) Skip(args ...interface{}) { } func (t *fakeT) Fatal(args ...interface{}) { - t.failed = true - t.failMsgs = append(t.failMsgs, fmt.Sprint(args...)) - panic(errAbort) + t.Log(args...) + t.FailNow() } func (t *fakeT) Parallel() {} @@ -417,7 +487,8 @@ func (t *fakeT) Log(args ...interface{}) { } func (t *fakeT) FailNow() { - t.Fatal("failed") + t.failed = true + panic(errAbort) } func (t *fakeT) Run(name string, f func(T)) { diff --git a/txtar/archive.go b/txtar/archive.go index 2fd22131..d14d4074 100644 --- a/txtar/archive.go +++ b/txtar/archive.go @@ -40,32 +40,22 @@ import ( "path/filepath" "strings" "unicode/utf8" + + "golang.org/x/tools/txtar" ) // An Archive is a collection of files. -type Archive struct { - Comment []byte - Files []File -} +type Archive = txtar.Archive // A File is a single file in an archive. -type File struct { - Name string // name of file ("foo/bar.txt") - Data []byte // text content of file -} +type File = txtar.File // Format returns the serialized form of an Archive. // It is assumed that the Archive data structure is well-formed: // a.Comment and all a.File[i].Data contain no file marker lines, // and all a.File[i].Name is non-empty. func Format(a *Archive) []byte { - var buf bytes.Buffer - buf.Write(fixNL(a.Comment)) - for _, f := range a.Files { - fmt.Fprintf(&buf, "-- %s --\n", f.Name) - buf.Write(fixNL(f.Data)) - } - return buf.Bytes() + return txtar.Format(a) } // ParseFile parses the named file as an archive. @@ -79,6 +69,9 @@ func ParseFile(file string) (*Archive, error) { // Parse parses the serialized form of an Archive. // The returned Archive holds slices of data. +// +// TODO use golang.org/x/tools/txtar.Parse when https://github.com/golang/go/issues/59264 +// is fixed. func Parse(data []byte) *Archive { a := new(Archive) var name string diff --git a/txtar/archive_test.go b/txtar/archive_test.go index 6eea75bf..7a74fd5b 100644 --- a/txtar/archive_test.go +++ b/txtar/archive_test.go @@ -44,7 +44,7 @@ hello world`, }, // Test CRLF input { - name: "basic", + name: "basicCRLF", text: "blah\r\n-- hello --\r\nhello\r\n", parsed: &Archive{ Comment: []byte("blah\r\n"),