blob: bb9c58889605b0961d1f03955b928ed4d7cd251f [file] [log] [blame]
# This test simulates a process watching for changes and reading files in
# module cache as a module is extracted.
#
# By default, we unzip a downloaded module into a temporary directory with a
# random name, then rename the directory into place. On Windows, this fails
# with ERROR_ACCESS_DENIED if another process (e.g., antivirus) opens files
# in the directory.
#
# Setting GODEBUG=modcacheunzipinplace=1 opts into new behavior: a downloaded
# module is unzipped in place. A .partial file is created elsewhere to indicate
# that the extraction is incomplete.
#
# Verifies golang.org/issue/36568.
[!windows] skip
[short] skip
# Control case: check that the default behavior fails.
# This is commented out to avoid flakiness. We can't reproduce the failure
# 100% of the time.
# ! go run downloader.go
# Experiment: check that the new behavior does not fail.
env GODEBUG=modcacheunzipinplace=1
go run downloader.go
-- go.mod --
module example.com/m
go 1.14
-- downloader.go --
package main
import (
"fmt"
"io/ioutil"
"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 := ioutil.TempDir("", "")
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
}
}
}
}