mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 06:15:33 +00:00
Merge remote-tracking branch 'origin/develop' into jae/literefactor4
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package autofile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -13,10 +14,14 @@ import (
|
||||
func TestSIGHUP(t *testing.T) {
|
||||
|
||||
// First, create an AutoFile writing to a tempfile dir
|
||||
file, name := cmn.Tempfile("sighup_test")
|
||||
if err := file.Close(); err != nil {
|
||||
file, err := ioutil.TempFile("", "sighup_test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tempfile: %v", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatalf("Error closing tempfile: %v", err)
|
||||
}
|
||||
name := file.Name()
|
||||
// Here is the actual AutoFile
|
||||
af, err := OpenAutoFile(name)
|
||||
if err != nil {
|
||||
|
||||
@@ -147,14 +147,13 @@ func TestSearch(t *testing.T) {
|
||||
|
||||
// Now search for each number
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Log("Testing for i", i)
|
||||
gr, match, err := g.Search("INFO", makeSearchFunc(i))
|
||||
require.NoError(t, err, "Failed to search for line")
|
||||
assert.True(t, match, "Expected Search to return exact match")
|
||||
require.NoError(t, err, "Failed to search for line, tc #%d", i)
|
||||
assert.True(t, match, "Expected Search to return exact match, tc #%d", i)
|
||||
line, err := gr.ReadLine()
|
||||
require.NoError(t, err, "Failed to read line after search")
|
||||
require.NoError(t, err, "Failed to read line after search, tc #%d", i)
|
||||
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", i)) {
|
||||
t.Fatal("Failed to get correct line")
|
||||
t.Fatalf("Failed to get correct line, tc #%d", i)
|
||||
}
|
||||
// Make sure we can continue to read from there.
|
||||
cur := i + 1
|
||||
@@ -165,16 +164,16 @@ func TestSearch(t *testing.T) {
|
||||
// OK!
|
||||
break
|
||||
} else {
|
||||
t.Fatal("Got EOF after the wrong INFO #")
|
||||
t.Fatalf("Got EOF after the wrong INFO #, tc #%d", i)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Fatal("Error reading line", err)
|
||||
t.Fatalf("Error reading line, tc #%d, err:\n%s", i, err)
|
||||
}
|
||||
if !strings.HasPrefix(line, "INFO ") {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, fmt.Sprintf("INFO %v ", cur)) {
|
||||
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v", cur, line)
|
||||
t.Fatalf("Unexpected INFO #. Expected %v got:\n%v, tc #%d", cur, line, i)
|
||||
}
|
||||
cur++
|
||||
}
|
||||
|
||||
@@ -8,13 +8,15 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BitArray is a thread-safe implementation of a bit array.
|
||||
type BitArray struct {
|
||||
mtx sync.Mutex
|
||||
Bits int `json:"bits"` // NOTE: persisted via reflect, must be exported
|
||||
Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported
|
||||
}
|
||||
|
||||
// There is no BitArray whose Size is 0. Use nil instead.
|
||||
// NewBitArray returns a new bit array.
|
||||
// It returns nil if the number of bits is zero.
|
||||
func NewBitArray(bits int) *BitArray {
|
||||
if bits <= 0 {
|
||||
return nil
|
||||
@@ -25,6 +27,7 @@ func NewBitArray(bits int) *BitArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the number of bits in the bitarray
|
||||
func (bA *BitArray) Size() int {
|
||||
if bA == nil {
|
||||
return 0
|
||||
@@ -32,7 +35,8 @@ func (bA *BitArray) Size() int {
|
||||
return bA.Bits
|
||||
}
|
||||
|
||||
// NOTE: behavior is undefined if i >= bA.Bits
|
||||
// GetIndex returns the bit at index i within the bit array.
|
||||
// The behavior is undefined if i >= bA.Bits
|
||||
func (bA *BitArray) GetIndex(i int) bool {
|
||||
if bA == nil {
|
||||
return false
|
||||
@@ -49,7 +53,8 @@ func (bA *BitArray) getIndex(i int) bool {
|
||||
return bA.Elems[i/64]&(uint64(1)<<uint(i%64)) > 0
|
||||
}
|
||||
|
||||
// NOTE: behavior is undefined if i >= bA.Bits
|
||||
// SetIndex sets the bit at index i within the bit array.
|
||||
// The behavior is undefined if i >= bA.Bits
|
||||
func (bA *BitArray) SetIndex(i int, v bool) bool {
|
||||
if bA == nil {
|
||||
return false
|
||||
@@ -71,6 +76,7 @@ func (bA *BitArray) setIndex(i int, v bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy returns a copy of the provided bit array.
|
||||
func (bA *BitArray) Copy() *BitArray {
|
||||
if bA == nil {
|
||||
return nil
|
||||
@@ -98,7 +104,9 @@ func (bA *BitArray) copyBits(bits int) *BitArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a BitArray of larger bits size.
|
||||
// Or returns a bit array resulting from a bitwise OR of the two bit arrays.
|
||||
// If the two bit-arrys have different lengths, Or right-pads the smaller of the two bit-arrays with zeroes.
|
||||
// Thus the size of the return value is the maximum of the two provided bit arrays.
|
||||
func (bA *BitArray) Or(o *BitArray) *BitArray {
|
||||
if bA == nil && o == nil {
|
||||
return nil
|
||||
@@ -110,7 +118,11 @@ func (bA *BitArray) Or(o *BitArray) *BitArray {
|
||||
return bA.Copy()
|
||||
}
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
o.mtx.Lock()
|
||||
defer func() {
|
||||
bA.mtx.Unlock()
|
||||
o.mtx.Unlock()
|
||||
}()
|
||||
c := bA.copyBits(MaxInt(bA.Bits, o.Bits))
|
||||
for i := 0; i < len(c.Elems); i++ {
|
||||
c.Elems[i] |= o.Elems[i]
|
||||
@@ -118,13 +130,19 @@ func (bA *BitArray) Or(o *BitArray) *BitArray {
|
||||
return c
|
||||
}
|
||||
|
||||
// Returns a BitArray of smaller bit size.
|
||||
// And returns a bit array resulting from a bitwise AND of the two bit arrays.
|
||||
// If the two bit-arrys have different lengths, this truncates the larger of the two bit-arrays from the right.
|
||||
// Thus the size of the return value is the minimum of the two provided bit arrays.
|
||||
func (bA *BitArray) And(o *BitArray) *BitArray {
|
||||
if bA == nil || o == nil {
|
||||
return nil
|
||||
}
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
o.mtx.Lock()
|
||||
defer func() {
|
||||
bA.mtx.Unlock()
|
||||
o.mtx.Unlock()
|
||||
}()
|
||||
return bA.and(o)
|
||||
}
|
||||
|
||||
@@ -136,12 +154,17 @@ func (bA *BitArray) and(o *BitArray) *BitArray {
|
||||
return c
|
||||
}
|
||||
|
||||
// Not returns a bit array resulting from a bitwise Not of the provided bit array.
|
||||
func (bA *BitArray) Not() *BitArray {
|
||||
if bA == nil {
|
||||
return nil // Degenerate
|
||||
}
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
return bA.not()
|
||||
}
|
||||
|
||||
func (bA *BitArray) not() *BitArray {
|
||||
c := bA.copy()
|
||||
for i := 0; i < len(c.Elems); i++ {
|
||||
c.Elems[i] = ^c.Elems[i]
|
||||
@@ -149,13 +172,20 @@ func (bA *BitArray) Not() *BitArray {
|
||||
return c
|
||||
}
|
||||
|
||||
// Sub subtracts the two bit-arrays bitwise, without carrying the bits.
|
||||
// This is essentially bA.And(o.Not()).
|
||||
// If bA is longer than o, o is right padded with zeroes.
|
||||
func (bA *BitArray) Sub(o *BitArray) *BitArray {
|
||||
if bA == nil || o == nil {
|
||||
// TODO: Decide if we should do 1's complement here?
|
||||
return nil
|
||||
}
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
o.mtx.Lock()
|
||||
defer func() {
|
||||
bA.mtx.Unlock()
|
||||
o.mtx.Unlock()
|
||||
}()
|
||||
if bA.Bits > o.Bits {
|
||||
c := bA.copy()
|
||||
for i := 0; i < len(o.Elems)-1; i++ {
|
||||
@@ -164,15 +194,15 @@ func (bA *BitArray) Sub(o *BitArray) *BitArray {
|
||||
i := len(o.Elems) - 1
|
||||
if i >= 0 {
|
||||
for idx := i * 64; idx < o.Bits; idx++ {
|
||||
// NOTE: each individual GetIndex() call to o is safe.
|
||||
c.setIndex(idx, c.getIndex(idx) && !o.GetIndex(idx))
|
||||
c.setIndex(idx, c.getIndex(idx) && !o.getIndex(idx))
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
return bA.and(o.Not()) // Note degenerate case where o == nil
|
||||
return bA.and(o.not()) // Note degenerate case where o == nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true iff all bits in the bit array are 0
|
||||
func (bA *BitArray) IsEmpty() bool {
|
||||
if bA == nil {
|
||||
return true // should this be opposite?
|
||||
@@ -187,6 +217,7 @@ func (bA *BitArray) IsEmpty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsFull returns true iff all bits in the bit array are 1.
|
||||
func (bA *BitArray) IsFull() bool {
|
||||
if bA == nil {
|
||||
return true
|
||||
@@ -207,6 +238,8 @@ func (bA *BitArray) IsFull() bool {
|
||||
return (lastElem+1)&((uint64(1)<<uint(lastElemBits))-1) == 0
|
||||
}
|
||||
|
||||
// PickRandom returns a random index in the bit array, and its value.
|
||||
// It uses the global randomness in `random.go` to get this index.
|
||||
func (bA *BitArray) PickRandom() (int, bool) {
|
||||
if bA == nil {
|
||||
return 0, false
|
||||
@@ -260,6 +293,8 @@ func (bA *BitArray) String() string {
|
||||
return bA.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented returns the same thing as String(), but applies the indent
|
||||
// at every 10th bit, and twice at every 50th bit.
|
||||
func (bA *BitArray) StringIndented(indent string) string {
|
||||
if bA == nil {
|
||||
return "nil-BitArray"
|
||||
@@ -295,6 +330,7 @@ func (bA *BitArray) stringIndented(indent string) string {
|
||||
return fmt.Sprintf("BA{%v:%v}", bA.Bits, strings.Join(lines, indent))
|
||||
}
|
||||
|
||||
// Bytes returns the byte representation of the bits within the bitarray.
|
||||
func (bA *BitArray) Bytes() []byte {
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
@@ -309,15 +345,18 @@ func (bA *BitArray) Bytes() []byte {
|
||||
return bytes
|
||||
}
|
||||
|
||||
// NOTE: other bitarray o is not locked when reading,
|
||||
// so if necessary, caller must copy or lock o prior to calling Update.
|
||||
// If bA is nil, does nothing.
|
||||
// Update sets the bA's bits to be that of the other bit array.
|
||||
// The copying begins from the begin of both bit arrays.
|
||||
func (bA *BitArray) Update(o *BitArray) {
|
||||
if bA == nil || o == nil {
|
||||
return
|
||||
}
|
||||
bA.mtx.Lock()
|
||||
defer bA.mtx.Unlock()
|
||||
o.mtx.Lock()
|
||||
defer func() {
|
||||
bA.mtx.Unlock()
|
||||
o.mtx.Unlock()
|
||||
}()
|
||||
|
||||
copy(bA.Elems, o.Elems)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
@@ -124,60 +123,6 @@ func MustWriteFile(filePath string, contents []byte, mode os.FileMode) {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteFileAtomic creates a temporary file with data and the perm given and
|
||||
// swaps it atomically with filename if successful.
|
||||
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
|
||||
var (
|
||||
dir = filepath.Dir(filename)
|
||||
tempFile = filepath.Join(dir, "write-file-atomic-"+RandStr(32))
|
||||
// Override in case it does exist, create in case it doesn't and force kernel
|
||||
// flush, which still leaves the potential of lingering disk cache.
|
||||
flag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC
|
||||
)
|
||||
|
||||
f, err := os.OpenFile(tempFile, flag, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Clean up in any case. Defer stacking order is last-in-first-out.
|
||||
defer os.Remove(f.Name())
|
||||
defer f.Close()
|
||||
|
||||
if n, err := f.Write(data); err != nil {
|
||||
return err
|
||||
} else if n < len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
// Close the file before renaming it, otherwise it will cause "The process
|
||||
// cannot access the file because it is being used by another process." on windows.
|
||||
f.Close()
|
||||
|
||||
return os.Rename(f.Name(), filename)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func Tempfile(prefix string) (*os.File, string) {
|
||||
file, err := ioutil.TempFile("", prefix)
|
||||
if err != nil {
|
||||
PanicCrisis(err)
|
||||
}
|
||||
return file, file.Name()
|
||||
}
|
||||
|
||||
func Tempdir(prefix string) (*os.File, string) {
|
||||
tempDir := os.TempDir() + "/" + prefix + RandStr(12)
|
||||
err := EnsureDir(tempDir, 0700)
|
||||
if err != nil {
|
||||
panic(Fmt("Error creating temp dir: %v", err))
|
||||
}
|
||||
dir, err := os.Open(tempDir)
|
||||
if err != nil {
|
||||
panic(Fmt("Error opening temp dir: %v", err))
|
||||
}
|
||||
return dir, tempDir
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func Prompt(prompt string, defaultValue string) (string, error) {
|
||||
|
||||
@@ -1,52 +1,10 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteFileAtomic(t *testing.T) {
|
||||
var (
|
||||
data = []byte(RandStr(RandIntn(2048)))
|
||||
old = RandBytes(RandIntn(2048))
|
||||
perm os.FileMode = 0600
|
||||
)
|
||||
|
||||
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rData, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, rData) {
|
||||
t.Fatalf("data mismatch: %v != %v", data, rData)
|
||||
}
|
||||
|
||||
stat, err := os.Stat(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if have, want := stat.Mode().Perm(), perm; have != want {
|
||||
t.Errorf("have %v, want %v", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoPath(t *testing.T) {
|
||||
// restore original gopath upon exit
|
||||
path := os.Getenv("GOPATH")
|
||||
|
||||
@@ -109,6 +109,10 @@ func RandInt63n(n int64) int64 {
|
||||
return grand.Int63n(n)
|
||||
}
|
||||
|
||||
func RandBool() bool {
|
||||
return grand.Bool()
|
||||
}
|
||||
|
||||
func RandFloat32() float32 {
|
||||
return grand.Float32()
|
||||
}
|
||||
@@ -274,6 +278,13 @@ func (r *Rand) Intn(n int) int {
|
||||
return i
|
||||
}
|
||||
|
||||
// Bool returns a uniformly random boolean
|
||||
func (r *Rand) Bool() bool {
|
||||
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
|
||||
// for reasoning behind computing like this
|
||||
return r.Int63()%2 == 0
|
||||
}
|
||||
|
||||
// Perm returns a pseudo-random permutation of n integers in [0, n).
|
||||
func (r *Rand) Perm(n int) []int {
|
||||
r.Lock()
|
||||
|
||||
128
libs/common/tempfile.go
Normal file
128
libs/common/tempfile.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
atomicWriteFilePrefix = "write-file-atomic-"
|
||||
// Maximum number of atomic write file conflicts before we start reseeding
|
||||
// (reduced from golang's default 10 due to using an increased randomness space)
|
||||
atomicWriteFileMaxNumConflicts = 5
|
||||
// Maximum number of attempts to make at writing the write file before giving up
|
||||
// (reduced from golang's default 10000 due to using an increased randomness space)
|
||||
atomicWriteFileMaxNumWriteAttempts = 1000
|
||||
// LCG constants from Donald Knuth MMIX
|
||||
// This LCG's has a period equal to 2**64
|
||||
lcgA = 6364136223846793005
|
||||
lcgC = 1442695040888963407
|
||||
// Create in case it doesn't exist and force kernel
|
||||
// flush, which still leaves the potential of lingering disk cache.
|
||||
// Never overwrites files
|
||||
atomicWriteFileFlag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC | os.O_EXCL
|
||||
)
|
||||
|
||||
var (
|
||||
atomicWriteFileRand uint64
|
||||
atomicWriteFileRandMu sync.Mutex
|
||||
)
|
||||
|
||||
func writeFileRandReseed() uint64 {
|
||||
// Scale the PID, to minimize the chance that two processes seeded at similar times
|
||||
// don't get the same seed. Note that PID typically ranges in [0, 2**15), but can be
|
||||
// up to 2**22 under certain configurations. We left bit-shift the PID by 20, so that
|
||||
// a PID difference of one corresponds to a time difference of 2048 seconds.
|
||||
// The important thing here is that now for a seed conflict, they would both have to be on
|
||||
// the correct nanosecond offset, and second-based offset, which is much less likely than
|
||||
// just a conflict with the correct nanosecond offset.
|
||||
return uint64(time.Now().UnixNano() + int64(os.Getpid()<<20))
|
||||
}
|
||||
|
||||
// Use a fast thread safe LCG for atomic write file names.
|
||||
// Returns a string corresponding to a 64 bit int.
|
||||
// If it was a negative int, the leading number is a 0.
|
||||
func randWriteFileSuffix() string {
|
||||
atomicWriteFileRandMu.Lock()
|
||||
r := atomicWriteFileRand
|
||||
if r == 0 {
|
||||
r = writeFileRandReseed()
|
||||
}
|
||||
|
||||
// Update randomness according to lcg
|
||||
r = r*lcgA + lcgC
|
||||
|
||||
atomicWriteFileRand = r
|
||||
atomicWriteFileRandMu.Unlock()
|
||||
// Can have a negative name, replace this in the following
|
||||
suffix := strconv.Itoa(int(r))
|
||||
if string(suffix[0]) == "-" {
|
||||
// Replace first "-" with "0". This is purely for UI clarity,
|
||||
// as otherwhise there would be two `-` in a row.
|
||||
suffix = strings.Replace(suffix, "-", "0", 1)
|
||||
}
|
||||
return suffix
|
||||
}
|
||||
|
||||
// WriteFileAtomic creates a temporary file with data and provided perm and
|
||||
// swaps it atomically with filename if successful.
|
||||
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) {
|
||||
// This implementation is inspired by the golang stdlibs method of creating
|
||||
// tempfiles. Notable differences are that we use different flags, a 64 bit LCG
|
||||
// and handle negatives differently.
|
||||
// The core reason we can't use golang's TempFile is that we must write
|
||||
// to the file synchronously, as we need this to persist to disk.
|
||||
// We also open it in write-only mode, to avoid concerns that arise with read.
|
||||
var (
|
||||
dir = filepath.Dir(filename)
|
||||
f *os.File
|
||||
)
|
||||
|
||||
nconflict := 0
|
||||
// Limit the number of attempts to create a file. Something is seriously
|
||||
// wrong if it didn't get created after 1000 attempts, and we don't want
|
||||
// an infinite loop
|
||||
i := 0
|
||||
for ; i < atomicWriteFileMaxNumWriteAttempts; i++ {
|
||||
name := filepath.Join(dir, atomicWriteFilePrefix+randWriteFileSuffix())
|
||||
f, err = os.OpenFile(name, atomicWriteFileFlag, perm)
|
||||
// If the file already exists, try a new file
|
||||
if os.IsExist(err) {
|
||||
// If the files exists too many times, start reseeding as we've
|
||||
// likely hit another instances seed.
|
||||
if nconflict++; nconflict > atomicWriteFileMaxNumConflicts {
|
||||
atomicWriteFileRandMu.Lock()
|
||||
atomicWriteFileRand = writeFileRandReseed()
|
||||
atomicWriteFileRandMu.Unlock()
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
if i == atomicWriteFileMaxNumWriteAttempts {
|
||||
return fmt.Errorf("Could not create atomic write file after %d attempts", i)
|
||||
}
|
||||
|
||||
// Clean up in any case. Defer stacking order is last-in-first-out.
|
||||
defer os.Remove(f.Name())
|
||||
defer f.Close()
|
||||
|
||||
if n, err := f.Write(data); err != nil {
|
||||
return err
|
||||
} else if n < len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
// Close the file before renaming it, otherwise it will cause "The process
|
||||
// cannot access the file because it is being used by another process." on windows.
|
||||
f.Close()
|
||||
|
||||
return os.Rename(f.Name(), filename)
|
||||
}
|
||||
138
libs/common/tempfile_test.go
Normal file
138
libs/common/tempfile_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package common
|
||||
|
||||
// Need access to internal variables, so can't use _test package
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
fmt "fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
testing "testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWriteFileAtomic(t *testing.T) {
|
||||
var (
|
||||
data = []byte(RandStr(RandIntn(2048)))
|
||||
old = RandBytes(RandIntn(2048))
|
||||
perm os.FileMode = 0600
|
||||
)
|
||||
|
||||
f, err := ioutil.TempFile("/tmp", "write-atomic-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rData, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, rData) {
|
||||
t.Fatalf("data mismatch: %v != %v", data, rData)
|
||||
}
|
||||
|
||||
stat, err := os.Stat(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if have, want := stat.Mode().Perm(), perm; have != want {
|
||||
t.Errorf("have %v, want %v", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests atomic write file when there is a single duplicate file.
|
||||
// Expected behavior is for a new file to be created, and the original write file to be unaltered.
|
||||
func TestWriteFileAtomicDuplicateFile(t *testing.T) {
|
||||
var (
|
||||
defaultSeed uint64 = 1
|
||||
testString = "This is a glorious test string"
|
||||
expectedString = "Did the test file's string appear here?"
|
||||
|
||||
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
|
||||
)
|
||||
// Create a file at the seed, and reset the seed.
|
||||
atomicWriteFileRand = defaultSeed
|
||||
firstFileRand := randWriteFileSuffix()
|
||||
atomicWriteFileRand = defaultSeed
|
||||
fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand
|
||||
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
|
||||
defer os.Remove(fname)
|
||||
// Defer here, in case there is a panic in WriteFileAtomic.
|
||||
defer os.Remove(fileToWrite)
|
||||
|
||||
require.Nil(t, err)
|
||||
f.WriteString(testString)
|
||||
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
|
||||
// Check that the first atomic file was untouched
|
||||
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
|
||||
require.Nil(t, err, "Error reading first atomic file")
|
||||
require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten")
|
||||
// Check that the resultant file is correct
|
||||
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
|
||||
require.Nil(t, err, "Error reading resultant file")
|
||||
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
|
||||
|
||||
// Check that the intermediate write file was deleted
|
||||
// Get the second write files' randomness
|
||||
atomicWriteFileRand = defaultSeed
|
||||
_ = randWriteFileSuffix()
|
||||
secondFileRand := randWriteFileSuffix()
|
||||
_, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand)
|
||||
require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted")
|
||||
}
|
||||
|
||||
// This tests atomic write file when there are many duplicate files.
|
||||
// Expected behavior is for a new file to be created under a completely new seed,
|
||||
// and the original write files to be unaltered.
|
||||
func TestWriteFileAtomicManyDuplicates(t *testing.T) {
|
||||
var (
|
||||
defaultSeed uint64 = 2
|
||||
testString = "This is a glorious test string, from file %d"
|
||||
expectedString = "Did any of the test file's string appear here?"
|
||||
|
||||
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
|
||||
)
|
||||
// Initialize all of the atomic write files
|
||||
atomicWriteFileRand = defaultSeed
|
||||
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
|
||||
fileRand := randWriteFileSuffix()
|
||||
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
|
||||
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
|
||||
require.Nil(t, err)
|
||||
f.WriteString(fmt.Sprintf(testString, i))
|
||||
defer os.Remove(fname)
|
||||
}
|
||||
|
||||
atomicWriteFileRand = defaultSeed
|
||||
// Defer here, in case there is a panic in WriteFileAtomic.
|
||||
defer os.Remove(fileToWrite)
|
||||
|
||||
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
|
||||
// Check that all intermittent atomic file were untouched
|
||||
atomicWriteFileRand = defaultSeed
|
||||
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
|
||||
fileRand := randWriteFileSuffix()
|
||||
fname := "/tmp/" + atomicWriteFilePrefix + fileRand
|
||||
firstAtomicFileBytes, err := ioutil.ReadFile(fname)
|
||||
require.Nil(t, err, "Error reading first atomic file")
|
||||
require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes,
|
||||
"atomic write file %d was overwritten", i)
|
||||
}
|
||||
|
||||
// Check that the resultant file is correct
|
||||
resultantFileBytes, err := ioutil.ReadFile(fileToWrite)
|
||||
require.Nil(t, err, "Error reading resultant file")
|
||||
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -17,8 +18,8 @@ func cleanupDBDir(dir, name string) {
|
||||
|
||||
func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
|
||||
// Default
|
||||
dir, dirname := cmn.Tempdir(fmt.Sprintf("test_backend_%s_", backend))
|
||||
defer dir.Close()
|
||||
dirname, err := ioutil.TempDir("", fmt.Sprintf("test_backend_%s_", backend))
|
||||
require.Nil(t, err)
|
||||
db := NewDB("testdb", backend, dirname)
|
||||
|
||||
// A nonexistent key should return nil, even if the key is empty
|
||||
|
||||
@@ -2,12 +2,12 @@ package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
@@ -61,9 +61,9 @@ func checkValuePanics(t *testing.T, itr Iterator) {
|
||||
}
|
||||
|
||||
func newTempDB(t *testing.T, backend DBBackendType) (db DB) {
|
||||
dir, dirname := cmn.Tempdir("db_common_test")
|
||||
dirname, err := ioutil.TempDir("", "db_common_test")
|
||||
require.Nil(t, err)
|
||||
db = NewDB("testdb", backend, dirname)
|
||||
dir.Close()
|
||||
return db
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user