mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-11-15 04:35:17 +00:00
461 lines
10 KiB
Go
461 lines
10 KiB
Go
|
// Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||
|
// of this source code is governed by a BSD-style license that can be found in
|
||
|
// the LICENSE file.
|
||
|
|
||
|
package vfs
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/cockroachdb/datadriven"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
func runTestCases(t *testing.T, testCases []string, fs *MemFS) {
|
||
|
var f File
|
||
|
for _, tc := range testCases {
|
||
|
s := strings.Split(tc, " ")[1:]
|
||
|
|
||
|
saveF := s[0] == "f" && s[1] == "="
|
||
|
if saveF {
|
||
|
s = s[2:]
|
||
|
}
|
||
|
|
||
|
fails := s[len(s)-1] == "fails"
|
||
|
if fails {
|
||
|
s = s[:len(s)-1]
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
fi os.FileInfo
|
||
|
g File
|
||
|
err error
|
||
|
)
|
||
|
switch s[0] {
|
||
|
case "create":
|
||
|
g, err = fs.Create(s[1])
|
||
|
case "link":
|
||
|
err = fs.Link(s[1], s[2])
|
||
|
case "open":
|
||
|
g, err = fs.Open(s[1])
|
||
|
case "openDir":
|
||
|
g, err = fs.OpenDir(s[1])
|
||
|
case "mkdirall":
|
||
|
err = fs.MkdirAll(s[1], 0755)
|
||
|
case "remove":
|
||
|
err = fs.Remove(s[1])
|
||
|
case "rename":
|
||
|
err = fs.Rename(s[1], s[2])
|
||
|
case "reuseForWrite":
|
||
|
g, err = fs.ReuseForWrite(s[1], s[2])
|
||
|
case "resetToSynced":
|
||
|
fs.ResetToSyncedState()
|
||
|
case "ignoreSyncs":
|
||
|
fs.SetIgnoreSyncs(true)
|
||
|
case "stopIgnoringSyncs":
|
||
|
fs.SetIgnoreSyncs(false)
|
||
|
case "f.write":
|
||
|
_, err = f.Write([]byte(s[1]))
|
||
|
case "f.sync":
|
||
|
err = f.Sync()
|
||
|
case "f.read":
|
||
|
n, _ := strconv.Atoi(s[1])
|
||
|
buf := make([]byte, n)
|
||
|
_, err = io.ReadFull(f, buf)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
if got, want := string(buf), s[3]; got != want {
|
||
|
t.Fatalf("%q: got %q, want %q", tc, got, want)
|
||
|
}
|
||
|
case "f.readat":
|
||
|
n, _ := strconv.Atoi(s[1])
|
||
|
off, _ := strconv.Atoi(s[2])
|
||
|
buf := make([]byte, n)
|
||
|
_, err = f.ReadAt(buf, int64(off))
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
if got, want := string(buf), s[4]; got != want {
|
||
|
t.Fatalf("%q: got %q, want %q", tc, got, want)
|
||
|
}
|
||
|
case "f.close":
|
||
|
f, err = nil, f.Close()
|
||
|
case "f.stat.name":
|
||
|
fi, err = f.Stat()
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
if got, want := fi.Name(), s[2]; got != want {
|
||
|
t.Fatalf("%q: got %q, want %q", tc, got, want)
|
||
|
}
|
||
|
default:
|
||
|
t.Fatalf("bad test case: %q", tc)
|
||
|
}
|
||
|
|
||
|
if saveF {
|
||
|
f, g = g, nil
|
||
|
} else if g != nil {
|
||
|
g.Close()
|
||
|
}
|
||
|
|
||
|
if fails {
|
||
|
if err == nil {
|
||
|
t.Fatalf("%q: got nil error, want non-nil", tc)
|
||
|
}
|
||
|
} else {
|
||
|
if err != nil {
|
||
|
t.Fatalf("%q: %v", tc, err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Both "" and "/" are allowed to be used to refer to the root of the FS
|
||
|
// for the purposes of cloning.
|
||
|
checkClonedIsEquivalent(t, fs, "")
|
||
|
checkClonedIsEquivalent(t, fs, "/")
|
||
|
}
|
||
|
|
||
|
// Test that the FS can be cloned and that the clone serializes identically.
|
||
|
func checkClonedIsEquivalent(t *testing.T, fs *MemFS, path string) {
|
||
|
t.Helper()
|
||
|
clone := NewMem()
|
||
|
cloned, err := Clone(fs, clone, path, path)
|
||
|
require.NoError(t, err)
|
||
|
require.True(t, cloned)
|
||
|
require.Equal(t, fs.String(), clone.String())
|
||
|
}
|
||
|
|
||
|
func TestBasics(t *testing.T) {
|
||
|
fs := NewMem()
|
||
|
testCases := []string{
|
||
|
// Create a top-level file.
|
||
|
"1a: create /foo",
|
||
|
// Create a child of that file. It should fail, since /foo is not a directory.
|
||
|
"2a: create /foo/x fails",
|
||
|
// Create a third-level file. It should fail, since /bar has not been created.
|
||
|
// Similarly, opening that file should fail.
|
||
|
"3a: create /bar/baz/y fails",
|
||
|
"3b: open /bar/baz/y fails",
|
||
|
// Make the /bar/baz directory; create a third-level file. Creation should now succeed.
|
||
|
"4a: mkdirall /bar/baz",
|
||
|
"4b: f = create /bar/baz/y",
|
||
|
"4c: f.stat.name == y",
|
||
|
// Write some data; read it back.
|
||
|
"5a: f.write abcde",
|
||
|
"5b: f.close",
|
||
|
"5c: f = open /bar/baz/y",
|
||
|
"5d: f.read 5 == abcde",
|
||
|
"5e: f.readat 2 1 == bc",
|
||
|
"5f: f.close",
|
||
|
// Link /bar/baz/y to /bar/baz/z. We should be able to read from both files
|
||
|
// and remove them independently.
|
||
|
"6a: link /bar/baz/y /bar/baz/z",
|
||
|
"6b: f = open /bar/baz/z",
|
||
|
"6c: f.read 5 == abcde",
|
||
|
"6d: f.close",
|
||
|
"6e: remove /bar/baz/z",
|
||
|
"6f: f = open /bar/baz/y",
|
||
|
"6g: f.read 5 == abcde",
|
||
|
"6h: f.close",
|
||
|
// Remove the file twice. The first should succeed, the second should fail.
|
||
|
"7a: remove /bar/baz/y",
|
||
|
"7b: remove /bar/baz/y fails",
|
||
|
"7c: open /bar/baz/y fails",
|
||
|
// Rename /foo to /goo. Trying to open /foo should succeed before the rename and
|
||
|
// fail afterwards, and vice versa for /goo.
|
||
|
"8a: open /foo",
|
||
|
"8b: open /goo fails",
|
||
|
"8c: rename /foo /goo",
|
||
|
"8d: open /foo fails",
|
||
|
"8e: open /goo",
|
||
|
// Create /bar/baz/z and rename /bar/baz to /bar/caz.
|
||
|
"9a: create /bar/baz/z",
|
||
|
"9b: open /bar/baz/z",
|
||
|
"9c: open /bar/caz/z fails",
|
||
|
"9d: rename /bar/baz /bar/caz",
|
||
|
"9e: open /bar/baz/z fails",
|
||
|
"9f: open /bar/caz/z",
|
||
|
// ReuseForWrite
|
||
|
"10a: reuseForWrite /bar/caz/z /bar/z",
|
||
|
"10b: open /bar/caz/z fails",
|
||
|
"10c: open /bar/z",
|
||
|
// Opening the root directory works.
|
||
|
"11a: f = open /",
|
||
|
"11b: f.stat.name == /",
|
||
|
}
|
||
|
runTestCases(t, testCases, fs)
|
||
|
}
|
||
|
|
||
|
func TestList(t *testing.T) {
|
||
|
fs := NewMem()
|
||
|
|
||
|
dirnames := []string{
|
||
|
"/bar",
|
||
|
"/foo/2",
|
||
|
}
|
||
|
for _, dirname := range dirnames {
|
||
|
err := fs.MkdirAll(dirname, 0755)
|
||
|
if err != nil {
|
||
|
t.Fatalf("MkdirAll %q: %v", dirname, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
filenames := []string{
|
||
|
"/a",
|
||
|
"/bar/baz",
|
||
|
"/foo/0",
|
||
|
"/foo/1",
|
||
|
"/foo/2/a",
|
||
|
"/foo/2/b",
|
||
|
"/foo/3",
|
||
|
"/foot",
|
||
|
}
|
||
|
for _, filename := range filenames {
|
||
|
f, err := fs.Create(filename)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Create %q: %v", filename, err)
|
||
|
}
|
||
|
if err := f.Close(); err != nil {
|
||
|
t.Fatalf("Close %q: %v", filename, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
{
|
||
|
got := fs.String()
|
||
|
const want = ` /
|
||
|
0 a
|
||
|
bar/
|
||
|
0 baz
|
||
|
foo/
|
||
|
0 0
|
||
|
0 1
|
||
|
2/
|
||
|
0 a
|
||
|
0 b
|
||
|
0 3
|
||
|
0 foot
|
||
|
`
|
||
|
if got != want {
|
||
|
t.Fatalf("String:\n----got----\n%s----want----\n%s", got, want)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
testCases := []string{
|
||
|
"/:a bar foo foot",
|
||
|
"/bar:baz",
|
||
|
"/bar/:baz",
|
||
|
"/baz:",
|
||
|
"/baz/:",
|
||
|
"/foo:0 1 2 3",
|
||
|
"/foo/:0 1 2 3",
|
||
|
"/foo/1:",
|
||
|
"/foo/1/:",
|
||
|
"/foo/2:a b",
|
||
|
"/foo/2/:a b",
|
||
|
"/foot:",
|
||
|
"/foot/:",
|
||
|
}
|
||
|
for _, tc := range testCases {
|
||
|
s := strings.Split(tc, ":")
|
||
|
list, _ := fs.List(s[0])
|
||
|
sort.Strings(list)
|
||
|
got := strings.Join(list, " ")
|
||
|
want := s[1]
|
||
|
if got != want {
|
||
|
t.Errorf("List %q: got %q, want %q", s[0], got, want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMemFile(t *testing.T) {
|
||
|
want := "foo"
|
||
|
f := NewMemFile([]byte(want))
|
||
|
buf, err := io.ReadAll(f)
|
||
|
if err != nil {
|
||
|
t.Fatalf("%v", err)
|
||
|
}
|
||
|
if got := string(buf); got != want {
|
||
|
t.Fatalf("got %q, want %q", got, want)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestStrictFS(t *testing.T) {
|
||
|
fs := NewStrictMem()
|
||
|
testCases := []string{
|
||
|
// Created file disappears if directory is not synced.
|
||
|
"1a: create /foo",
|
||
|
"1b: open /foo",
|
||
|
"1c: resetToSynced",
|
||
|
"1d: open /foo fails",
|
||
|
|
||
|
// Create directory and a file in it and write and read from it.
|
||
|
"2a: mkdirall /bar",
|
||
|
"2b: f = create /bar/y",
|
||
|
"2c: f.stat.name == y",
|
||
|
// Write some data; read it back.
|
||
|
"2d: f.write abcde",
|
||
|
"2e: f.close",
|
||
|
"2f: f = open /bar/y",
|
||
|
"2g: f.read 5 == abcde",
|
||
|
"2h: f.close",
|
||
|
"2i: open /bar",
|
||
|
|
||
|
// Resetting causes both the directory and file to disappear.
|
||
|
"3a: resetToSynced",
|
||
|
"3b: openDir /bar fails",
|
||
|
"3c: open /bar/y fails",
|
||
|
|
||
|
// Create the directory and file again. Link the file to another file in the same dir,
|
||
|
// and to a file in the root dir. Sync the root dir. After reset, the created dir and the
|
||
|
// file in the root dir are the only ones visible.
|
||
|
"4a: mkdirall /bar",
|
||
|
"4b: create /bar/y",
|
||
|
"4c: f = openDir /",
|
||
|
"4d: f.sync",
|
||
|
"4e: f.close",
|
||
|
"4f: link /bar/y /bar/z",
|
||
|
"4g: link /bar/y /z",
|
||
|
"4h: f = openDir /",
|
||
|
"4i: f.sync",
|
||
|
"4j: f.close",
|
||
|
"4k: resetToSynced",
|
||
|
"4l: openDir /bar",
|
||
|
"4m: open /bar/y fails",
|
||
|
"4n: open /bar/z fails",
|
||
|
"4o: open /z",
|
||
|
|
||
|
// Create the file in the directory again and this time sync /bar directory. The file is
|
||
|
// preserved after reset.
|
||
|
"5a: create /bar/y",
|
||
|
"5b: f = openDir /bar",
|
||
|
"5c: f.sync",
|
||
|
"5d: f.close",
|
||
|
"5e: resetToSynced",
|
||
|
"5f: openDir /bar",
|
||
|
"5g: open /bar/y",
|
||
|
|
||
|
// Unsynced data in the file is lost on reset.
|
||
|
"5a: f = create /bar/y",
|
||
|
"5b: f.write a",
|
||
|
"5c: f.sync",
|
||
|
"5d: f.write b",
|
||
|
"5e: f.close",
|
||
|
"5f: f = openDir /bar",
|
||
|
"5g: f.sync",
|
||
|
"5h: f.close",
|
||
|
"5i: resetToSynced",
|
||
|
"5j: f = open /bar/y",
|
||
|
"5k: f.read 1 = a",
|
||
|
"5l: f.read 1 fails",
|
||
|
"5m: f.close",
|
||
|
|
||
|
// reuseForWrite works correctly in strict mode in that unsynced data does not overwrite
|
||
|
// previous contents when a reset happens.
|
||
|
"6a: f = create /z",
|
||
|
"6b: f.write abcdefgh",
|
||
|
"6c: f.sync",
|
||
|
"6d: f.close",
|
||
|
"6e: f = reuseForWrite /z /y",
|
||
|
"6f: f.write x",
|
||
|
"6g: f.sync",
|
||
|
"6h: f.write y", // will be lost
|
||
|
"6i: f.close",
|
||
|
"6j: f = openDir /",
|
||
|
"6k: f.sync",
|
||
|
"6l: f.close",
|
||
|
"6m: resetToSynced",
|
||
|
"6n: f = open /y",
|
||
|
"6o: f.read 8 = xbcdefgh",
|
||
|
"6p: f.close",
|
||
|
|
||
|
// Ignore syncs.
|
||
|
"7a: f = create /z",
|
||
|
"7b: f.write a",
|
||
|
"7c: f.sync",
|
||
|
"7d: ignoreSyncs",
|
||
|
"7e: f.write b",
|
||
|
"7f: f.sync",
|
||
|
"7g: f.close",
|
||
|
"7h: stopIgnoringSyncs",
|
||
|
"7e: f = openDir /",
|
||
|
"7f: f.sync",
|
||
|
"7g: f.close",
|
||
|
"7h: resetToSynced",
|
||
|
"7i: f = open /z",
|
||
|
"7j: f.read 1 = a",
|
||
|
"7k: f.read 1 fails",
|
||
|
"7l: f.close",
|
||
|
}
|
||
|
runTestCases(t, testCases, fs)
|
||
|
}
|
||
|
|
||
|
func TestMemFSLock(t *testing.T) {
|
||
|
filesystems := map[string]FS{}
|
||
|
fileLocks := map[string]io.Closer{}
|
||
|
|
||
|
datadriven.RunTest(t, "testdata/memfs_lock", func(t *testing.T, td *datadriven.TestData) string {
|
||
|
switch td.Cmd {
|
||
|
case "mkfs":
|
||
|
for _, arg := range td.CmdArgs {
|
||
|
filesystems[arg.String()] = NewMem()
|
||
|
}
|
||
|
return "OK"
|
||
|
|
||
|
// lock fs=<filesystem-name> handle=<handle> path=<path>
|
||
|
case "lock":
|
||
|
var filesystemName string
|
||
|
var path string
|
||
|
var handle string
|
||
|
td.ScanArgs(t, "fs", &filesystemName)
|
||
|
td.ScanArgs(t, "path", &path)
|
||
|
td.ScanArgs(t, "handle", &handle)
|
||
|
fs := filesystems[filesystemName]
|
||
|
if fs == nil {
|
||
|
return fmt.Sprintf("filesystem %q doesn't exist", filesystemName)
|
||
|
}
|
||
|
l, err := fs.Lock(path)
|
||
|
if err != nil {
|
||
|
return err.Error()
|
||
|
}
|
||
|
fileLocks[handle] = l
|
||
|
return "OK"
|
||
|
|
||
|
// mkdirall fs=<filesystem-name> path=<path>
|
||
|
case "mkdirall":
|
||
|
var filesystemName string
|
||
|
var path string
|
||
|
td.ScanArgs(t, "fs", &filesystemName)
|
||
|
td.ScanArgs(t, "path", &path)
|
||
|
fs := filesystems[filesystemName]
|
||
|
if fs == nil {
|
||
|
return fmt.Sprintf("filesystem %q doesn't exist", filesystemName)
|
||
|
}
|
||
|
err := fs.MkdirAll(path, 0755)
|
||
|
if err != nil {
|
||
|
return err.Error()
|
||
|
}
|
||
|
return "OK"
|
||
|
|
||
|
// close handle=<handle>
|
||
|
case "close":
|
||
|
var handle string
|
||
|
td.ScanArgs(t, "handle", &handle)
|
||
|
err := fileLocks[handle].Close()
|
||
|
delete(fileLocks, handle)
|
||
|
if err != nil {
|
||
|
return err.Error()
|
||
|
}
|
||
|
return "OK"
|
||
|
default:
|
||
|
return fmt.Sprintf("unrecognized command %q", td.Cmd)
|
||
|
}
|
||
|
})
|
||
|
}
|