github.com/nalekseevs/itns-golangci-lint@v1.0.2/internal/renameio/renameio_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !plan9 6 // +build !plan9 7 8 package renameio 9 10 import ( 11 "encoding/binary" 12 "math/rand" 13 "os" 14 "path/filepath" 15 "runtime" 16 "sync" 17 "sync/atomic" 18 "syscall" 19 "testing" 20 "time" 21 22 "github.com/nalekseevs/itns-golangci-lint/internal/robustio" 23 ) 24 25 func TestConcurrentReadsAndWrites(t *testing.T) { 26 dir, err := os.MkdirTemp("", "renameio") 27 if err != nil { 28 t.Fatal(err) 29 } 30 defer os.RemoveAll(dir) 31 path := filepath.Join(dir, "blob.bin") 32 33 const chunkWords = 8 << 10 34 buf := make([]byte, 2*chunkWords*8) 35 for i := uint64(0); i < 2*chunkWords; i++ { 36 binary.LittleEndian.PutUint64(buf[i*8:], i) 37 } 38 39 var attempts int64 = 128 40 if !testing.Short() { 41 attempts *= 16 42 } 43 const parallel = 32 44 45 var sem = make(chan bool, parallel) 46 47 var ( 48 writeSuccesses, readSuccesses int64 // atomic 49 writeErrnoSeen, readErrnoSeen sync.Map 50 ) 51 52 for n := attempts; n > 0; n-- { 53 sem <- true 54 go func() { 55 defer func() { <-sem }() 56 57 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) 58 offset := rand.Intn(chunkWords) 59 chunk := buf[offset*8 : (offset+chunkWords)*8] 60 if err := WriteFile(path, chunk, 0666); err == nil { 61 atomic.AddInt64(&writeSuccesses, 1) 62 } else if robustio.IsEphemeralError(err) { 63 var ( 64 dup bool 65 ) 66 if errno, ok := err.(syscall.Errno); ok { 67 _, dup = writeErrnoSeen.LoadOrStore(errno, true) 68 } 69 if !dup { 70 t.Logf("ephemeral error: %v", err) 71 } 72 } else { 73 t.Errorf("unexpected error: %v", err) 74 } 75 76 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) 77 data, err := ReadFile(path) 78 if err == nil { 79 atomic.AddInt64(&readSuccesses, 1) 80 } else if robustio.IsEphemeralError(err) { 81 var ( 82 dup bool 83 ) 84 if errno, ok := err.(syscall.Errno); ok { 85 _, dup = readErrnoSeen.LoadOrStore(errno, true) 86 } 87 if !dup { 88 t.Logf("ephemeral error: %v", err) 89 } 90 return 91 } else { 92 t.Errorf("unexpected error: %v", err) 93 return 94 } 95 96 if len(data) != 8*chunkWords { 97 t.Errorf("read %d bytes, but each write is a %d-byte file", len(data), 8*chunkWords) 98 return 99 } 100 101 u := binary.LittleEndian.Uint64(data) 102 for i := 1; i < chunkWords; i++ { 103 next := binary.LittleEndian.Uint64(data[i*8:]) 104 if next != u+1 { 105 t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i) 106 return 107 } 108 u = next 109 } 110 }() 111 } 112 113 for n := parallel; n > 0; n-- { 114 sem <- true 115 } 116 117 var minWriteSuccesses int64 = attempts 118 if runtime.GOOS == "windows" { 119 // Windows produces frequent "Access is denied" errors under heavy rename load. 120 // As long as those are the only errors and *some* of the writes succeed, we're happy. 121 minWriteSuccesses = attempts / 4 122 } 123 124 if writeSuccesses < minWriteSuccesses { 125 t.Errorf("%d (of %d) writes succeeded; want ≥ %d", writeSuccesses, attempts, minWriteSuccesses) 126 } else { 127 t.Logf("%d (of %d) writes succeeded (ok: ≥ %d)", writeSuccesses, attempts, minWriteSuccesses) 128 } 129 130 var minReadSuccesses int64 = attempts 131 132 switch runtime.GOOS { 133 case "windows": 134 // Windows produces frequent "Access is denied" errors under heavy rename load. 135 // As long as those are the only errors and *some* of the reads succeed, we're happy. 136 minReadSuccesses = attempts / 4 137 138 case "darwin": 139 // The filesystem on macOS 10.14 occasionally fails with "no such file or 140 // directory" errors. See https://golang.org/issue/33041. The flake rate is 141 // fairly low, so ensure that at least 75% of attempts succeed. 142 minReadSuccesses = attempts - (attempts / 4) 143 } 144 145 if readSuccesses < minReadSuccesses { 146 t.Errorf("%d (of %d) reads succeeded; want ≥ %d", readSuccesses, attempts, minReadSuccesses) 147 } else { 148 t.Logf("%d (of %d) reads succeeded (ok: ≥ %d)", readSuccesses, attempts, minReadSuccesses) 149 } 150 }