github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/lockedfile/transform_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  // js does not support inter-process file locking.
     6  //
     7  //go:build !js
     8  // +build !js
     9  
    10  package lockedfile_test
    11  
    12  import (
    13  	"bytes"
    14  	"encoding/binary"
    15  	"math/rand"
    16  	"path/filepath"
    17  	"testing"
    18  	"time"
    19  
    20  	"golang.org/x/tools/internal/lockedfile"
    21  )
    22  
    23  func isPowerOf2(x int) bool {
    24  	return x > 0 && x&(x-1) == 0
    25  }
    26  
    27  func roundDownToPowerOf2(x int) int {
    28  	if x <= 0 {
    29  		panic("nonpositive x")
    30  	}
    31  	bit := 1
    32  	for x != bit {
    33  		x = x &^ bit
    34  		bit <<= 1
    35  	}
    36  	return x
    37  }
    38  
    39  func TestTransform(t *testing.T) {
    40  	dir, remove := mustTempDir(t)
    41  	defer remove()
    42  	path := filepath.Join(dir, "blob.bin")
    43  
    44  	const maxChunkWords = 8 << 10
    45  	buf := make([]byte, 2*maxChunkWords*8)
    46  	for i := uint64(0); i < 2*maxChunkWords; i++ {
    47  		binary.LittleEndian.PutUint64(buf[i*8:], i)
    48  	}
    49  	if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
    50  		t.Fatal(err)
    51  	}
    52  
    53  	var attempts int64 = 128
    54  	if !testing.Short() {
    55  		attempts *= 16
    56  	}
    57  	const parallel = 32
    58  
    59  	var sem = make(chan bool, parallel)
    60  
    61  	for n := attempts; n > 0; n-- {
    62  		sem <- true
    63  		go func() {
    64  			defer func() { <-sem }()
    65  
    66  			time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
    67  			chunkWords := roundDownToPowerOf2(rand.Intn(maxChunkWords) + 1)
    68  			offset := rand.Intn(chunkWords)
    69  
    70  			err := lockedfile.Transform(path, func(data []byte) (chunk []byte, err error) {
    71  				chunk = buf[offset*8 : (offset+chunkWords)*8]
    72  
    73  				if len(data)&^7 != len(data) {
    74  					t.Errorf("read %d bytes, but each write is an integer multiple of 8 bytes", len(data))
    75  					return chunk, nil
    76  				}
    77  
    78  				words := len(data) / 8
    79  				if !isPowerOf2(words) {
    80  					t.Errorf("read %d 8-byte words, but each write is a power-of-2 number of words", words)
    81  					return chunk, nil
    82  				}
    83  
    84  				u := binary.LittleEndian.Uint64(data)
    85  				for i := 1; i < words; i++ {
    86  					next := binary.LittleEndian.Uint64(data[i*8:])
    87  					if next != u+1 {
    88  						t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i)
    89  						return chunk, nil
    90  					}
    91  					u = next
    92  				}
    93  
    94  				return chunk, nil
    95  			})
    96  
    97  			if err != nil {
    98  				t.Errorf("unexpected error from Transform: %v", err)
    99  			}
   100  		}()
   101  	}
   102  
   103  	for n := parallel; n > 0; n-- {
   104  		sem <- true
   105  	}
   106  }