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  }