blob: 231babd0c09cbaa35cc94d2ffa5f2a26cec2b197 [file] [log] [blame]
# This test simulates a process watching for changes and reading files in
# module cache as a module is extracted.
#
# Before Go 1.16, we extracted each module zip to a temporary directory with
# a random name, then renamed that into place with os.Rename. On Windows,
# this failed with ERROR_ACCESS_DENIED when another process (usually an
# anti-virus scanner) opened files in the temporary directory. This test
# simulates that behavior, verifying golang.org/issue/36568.
#
# Since 1.16, we extract to the final directory, but we create a .partial file
# so that if we crash, other processes know the directory is incomplete.
[!windows] skip
[short] skip
go run downloader.go
-- go.mod --
module example.com/m
go 1.14
-- downloader.go --
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
// run repeatedly downloads a module while opening files in the module cache
// in a background goroutine.
//
// run uses a different temporary module cache in each iteration so that we
// don't need to clean the cache or synchronize closing files after each
// iteration.
func run() (err error) {
tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer func() {
if rmErr := os.RemoveAll(tmpDir); err == nil && rmErr != nil {
err = rmErr
}
}()
for i := 0; i < 10; i++ {
gopath := filepath.Join(tmpDir, fmt.Sprintf("gopath%d", i))
var err error
done := make(chan struct{})
go func() {
err = download(gopath)
close(done)
}()
readCache(gopath, done)
if err != nil {
return err
}
}
return nil
}
// download downloads a module into the given cache using 'go mod download'.
func download(gopath string) error {
cmd := exec.Command("go", "mod", "download", "-modcacherw", "rsc.io/quote@v1.5.2")
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), "GOPATH="+gopath)
return cmd.Run()
}
// readCache repeatedly globs for go.mod files in the given cache, then opens
// those files for reading. When the done chan is closed, readCache closes
// files and returns.
func readCache(gopath string, done <-chan struct{}) {
files := make(map[string]*os.File)
defer func() {
for _, f := range files {
f.Close()
}
}()
pattern := filepath.Join(gopath, "pkg/mod/rsc.io/quote@v1.5.2*/go.mod")
for {
select {
case <-done:
return
default:
}
names, _ := filepath.Glob(pattern)
for _, name := range names {
if files[name] != nil {
continue
}
f, _ := os.Open(name)
if f != nil {
files[name] = f
}
}
}
}