726 lines
20 KiB
Go
726 lines
20 KiB
Go
package virtfs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// NodeOption tests
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestNodeOptions(t *testing.T) {
|
|
t.Run("Stream", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, StreamRaw(func(ctx context.Context, base string) ([]byte, string, error) {
|
|
return nil, "", nil
|
|
}))
|
|
if spec.Stream == nil {
|
|
t.Error("Stream not set")
|
|
}
|
|
})
|
|
|
|
t.Run("BlockOnce", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, BlockOnceRaw(func(ctx context.Context, base string) ([]byte, string, error) {
|
|
return nil, "", nil
|
|
}))
|
|
if spec.BlockOnce == nil {
|
|
t.Error("BlockOnce not set")
|
|
}
|
|
})
|
|
|
|
t.Run("Rdwr", func(t *testing.T) {
|
|
spec := FileNode("test", 0666, Rdwr(func(_ context.Context, data []byte) ([]byte, error) {
|
|
return nil, nil
|
|
}))
|
|
if spec.Rdwr == nil {
|
|
t.Error("Rdwr not set")
|
|
}
|
|
})
|
|
|
|
t.Run("StatOverride", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, StatOverride(func() os.FileInfo {
|
|
return &SyntheticFileInfo{Name_: "test"}
|
|
}))
|
|
if spec.Stat == nil {
|
|
t.Error("Stat not set")
|
|
}
|
|
})
|
|
|
|
t.Run("UID", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, UID("owner"))
|
|
if spec.UID != "owner" {
|
|
t.Errorf("UID = %q, want 'owner'", spec.UID)
|
|
}
|
|
})
|
|
|
|
t.Run("GID", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, GID("group"))
|
|
if spec.GID != "group" {
|
|
t.Errorf("GID = %q, want 'group'", spec.GID)
|
|
}
|
|
})
|
|
|
|
t.Run("Doc", func(t *testing.T) {
|
|
spec := FileNode("test", 0444, Doc("description"))
|
|
if spec.Desc != "description" {
|
|
t.Errorf("Desc = %q", spec.Desc)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Tree tests
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestTreeSplit(t *testing.T) {
|
|
parent := &Tree{perm: 0755, children: make(map[string]*Tree)}
|
|
child := &Tree{perm: 0755}
|
|
parent.children["sub"] = child
|
|
|
|
c, rest, ok := parent.split("sub")
|
|
if !ok || c != child || rest != "" {
|
|
t.Error("split exact match failed")
|
|
}
|
|
|
|
c2, rest2, ok2 := parent.split("sub/file")
|
|
if !ok2 || c2 != child || rest2 != "file" {
|
|
t.Error("split prefix match failed")
|
|
}
|
|
|
|
_, _, ok3 := parent.split("other")
|
|
if ok3 {
|
|
t.Error("split should not find 'other'")
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// fileConfig tests
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestFileConfigMethods(t *testing.T) {
|
|
fc := &fileConfig{
|
|
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: "test"}, nil },
|
|
ReadFn: func() ([]byte, error) { return []byte("content"), nil },
|
|
WriteFn: func(data []byte) error { return nil },
|
|
BlockingReadFn: func(ctx context.Context, base string) ([]byte, string, error) { return nil, "", nil },
|
|
}
|
|
|
|
fi, err := fc.Stat()
|
|
if err != nil || fi.Name() != "test" {
|
|
t.Error("Stat failed")
|
|
}
|
|
data, err := fc.Read()
|
|
if err != nil || string(data) != "content" {
|
|
t.Error("Read failed")
|
|
}
|
|
if err := fc.Write([]byte("x")); err != nil {
|
|
t.Error("Write failed")
|
|
}
|
|
if _, _, err := fc.BlockingRead(context.Background(), ""); err != nil {
|
|
t.Error("BlockingRead failed")
|
|
}
|
|
}
|
|
|
|
func TestFileConfigModes(t *testing.T) {
|
|
fc := &fileConfig{}
|
|
if fc.BlockingReadMode() || fc.StreamMode() || fc.RdwrMode() {
|
|
t.Error("default modes should be false")
|
|
}
|
|
|
|
if !(&fileConfig{BlockingRead_: true}).BlockingReadMode() {
|
|
t.Error("BlockingReadMode should be true")
|
|
}
|
|
if !(&fileConfig{StreamMode_: true}).StreamMode() {
|
|
t.Error("StreamMode should be true")
|
|
}
|
|
if !(&fileConfig{Rdwr_: true}).RdwrMode() {
|
|
t.Error("RdwrMode should be true")
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Builder tests - delete/rename operations
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestDeleteDir(t *testing.T) {
|
|
var deleted bool
|
|
spec := DirNode("/",
|
|
Each("{id}", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{{
|
|
Name: "item",
|
|
Remove: func() error { deleted = true; return nil },
|
|
Children: []FsNodeDecl{
|
|
FileNode("data", 0444),
|
|
},
|
|
}}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
err := tree.Delete("item")
|
|
if err != nil {
|
|
t.Fatalf("Delete: %v", err)
|
|
}
|
|
if !deleted {
|
|
t.Error("remove handler not called")
|
|
}
|
|
}
|
|
|
|
func TestDeleteDirNotFound(t *testing.T) {
|
|
spec := DirNode("/", FileNode("file", 0444))
|
|
tree := BuildTree(spec)
|
|
if err := tree.Delete("nonexistent"); err == nil {
|
|
t.Error("Delete should fail for nonexistent")
|
|
}
|
|
}
|
|
|
|
func TestRenameDir(t *testing.T) {
|
|
var newName string
|
|
spec := DirNode("/",
|
|
Each("{id}", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{{
|
|
Name: "item",
|
|
Rename: func(n string) error { newName = n; return nil },
|
|
Children: []FsNodeDecl{
|
|
FileNode("data", 0444),
|
|
},
|
|
}}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
if err := tree.Rename("item", "newitem"); err != nil {
|
|
t.Fatalf("Rename: %v", err)
|
|
}
|
|
if newName != "newitem" {
|
|
t.Errorf("newName = %q, want 'newitem'", newName)
|
|
}
|
|
}
|
|
|
|
func TestRenameDirNotFound(t *testing.T) {
|
|
spec := DirNode("/", FileNode("file", 0444))
|
|
tree := BuildTree(spec)
|
|
if err := tree.Rename("nonexistent", "new"); err == nil {
|
|
t.Error("Rename should fail for nonexistent")
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Builder tests - blocking modes
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestOpenLeafBlockOnce(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("blocking", 0444, BlockOnceRaw(func(ctx context.Context, base string) ([]byte, string, error) {
|
|
return []byte("data"), "next", nil
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("blocking")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if !f.BlockingReadMode() {
|
|
t.Error("expected BlockingReadMode")
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafStream(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("streaming", 0444, StreamRaw(func(ctx context.Context, base string) ([]byte, string, error) {
|
|
return []byte("stream"), "cursor", nil
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("streaming")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if !f.StreamMode() {
|
|
t.Error("expected StreamMode")
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafRdwr(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("request", 0666, Rdwr(func(_ context.Context, data []byte) ([]byte, error) {
|
|
return append([]byte("echo:"), data...), nil
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("request")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if !f.RdwrMode() {
|
|
t.Error("expected RdwrMode")
|
|
}
|
|
if err := f.Write([]byte("test")); err != nil {
|
|
t.Fatalf("Write: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data) != "echo:test" {
|
|
t.Errorf("data = %q, want 'echo:test'", data)
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafRdwrError(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("request", 0666, Rdwr(func(_ context.Context, data []byte) ([]byte, error) {
|
|
return nil, errors.New("request error")
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("request")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if err := f.Write([]byte("test")); err == nil {
|
|
t.Error("Write should fail when request returns error")
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafRdwrWithFallbackRead(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("request", 0666,
|
|
Read(func() ([]byte, error) { return []byte("fallback"), nil }),
|
|
Rdwr(func(_ context.Context, data []byte) ([]byte, error) { return []byte("response"), nil }),
|
|
),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("request")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data) != "fallback" {
|
|
t.Errorf("data = %q, want 'fallback'", data)
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafRdwrCancelOnClose(t *testing.T) {
|
|
canceled := make(chan struct{})
|
|
started := make(chan struct{})
|
|
spec := DirNode("/",
|
|
FileNode("request", 0666, Rdwr(func(ctx context.Context, data []byte) ([]byte, error) {
|
|
close(started)
|
|
<-ctx.Done()
|
|
close(canceled)
|
|
return nil, ctx.Err()
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("request")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
|
|
// Start the request in a goroutine
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- f.Write([]byte("test"))
|
|
}()
|
|
|
|
// Wait for request to start
|
|
<-started
|
|
|
|
// Close should cancel the context
|
|
f.Close()
|
|
|
|
// Verify context was canceled
|
|
select {
|
|
case <-canceled:
|
|
// Success
|
|
case <-time.After(time.Second):
|
|
t.Fatal("context was not canceled on close")
|
|
}
|
|
|
|
// Write should return with context canceled error
|
|
select {
|
|
case err := <-done:
|
|
if err == nil || err != context.Canceled {
|
|
t.Errorf("Write error = %v, want context.Canceled", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Write did not return after close")
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafNoWriteHandler(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("readonly", 0444, Read(func() ([]byte, error) { return []byte("data"), nil })),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("readonly")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if err := f.Write([]byte("test")); err == nil {
|
|
t.Error("Write should fail on read-only file")
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafNoReadHandler(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("noread", 0222, Write(func(data []byte) error { return nil })),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("noread")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Errorf("data = %q, want empty", data)
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafStatWithCustomStat(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("custom", 0444, StatOverride(func() os.FileInfo {
|
|
return &SyntheticFileInfo{Name_: "customname", Size_: 999, Mode_: 0777}
|
|
})),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("custom")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
t.Fatalf("Stat: %v", err)
|
|
}
|
|
if fi.Name() != "customname" || fi.Size() != 999 {
|
|
t.Errorf("got Name=%q Size=%d", fi.Name(), fi.Size())
|
|
}
|
|
}
|
|
|
|
func TestOpenLeafDefaultBlockingRead(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("normal", 0444, Read(func() ([]byte, error) { return []byte("data"), nil })),
|
|
)
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("normal")
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
if _, _, err := f.BlockingRead(context.Background(), ""); err == nil {
|
|
t.Error("BlockingRead should fail for non-blocking file")
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Builder tests - validation
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestValidateDirWithFileHandlers(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("should panic for dir with file handlers")
|
|
}
|
|
}()
|
|
|
|
spec := DirNode("/",
|
|
FileNode("child", 0444),
|
|
Read(func() ([]byte, error) { return nil, nil }),
|
|
)
|
|
BuildTree(spec)
|
|
}
|
|
|
|
func TestValidateMultipleBlockingModes(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("should panic for multiple blocking modes")
|
|
}
|
|
}()
|
|
|
|
spec := DirNode("/",
|
|
FileNode("bad", 0444,
|
|
BlockOnceRaw(func(context.Context, string) ([]byte, string, error) { return nil, "", nil }),
|
|
StreamRaw(func(context.Context, string) ([]byte, string, error) { return nil, "", nil }),
|
|
),
|
|
)
|
|
BuildTree(spec)
|
|
}
|
|
|
|
func TestValidateTemplateWithoutBindings(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("should panic for template without bindings")
|
|
}
|
|
}()
|
|
|
|
spec := DirNode("/", FileNode("{id}", 0444))
|
|
BuildTree(spec)
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Builder tests - Each with closure capture
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestEachClosureCapture(t *testing.T) {
|
|
type Item struct{ Name, Value string }
|
|
items := []Item{{"foo", "hello"}, {"bar", "world"}}
|
|
|
|
spec := DirNode("/",
|
|
Each("{item}", func() ([]FsNodeDecl, error) {
|
|
var out []FsNodeDecl
|
|
for _, item := range items {
|
|
it := item
|
|
out = append(out, FsNodeDecl{
|
|
Name: it.Name,
|
|
Children: []FsNodeDecl{
|
|
FileNode("value", 0444, Read(func() ([]byte, error) {
|
|
return []byte(it.Value), nil
|
|
})),
|
|
},
|
|
})
|
|
}
|
|
return out, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
|
|
// Read foo/value
|
|
f, err := tree.Open("foo/value")
|
|
if err != nil {
|
|
t.Fatalf("Open foo/value: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data) != "hello" {
|
|
t.Errorf("foo/value = %q, want 'hello'", data)
|
|
}
|
|
|
|
// Read bar/value
|
|
f2, err := tree.Open("bar/value")
|
|
if err != nil {
|
|
t.Fatalf("Open bar/value: %v", err)
|
|
}
|
|
data2, err := f2.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data2) != "world" {
|
|
t.Errorf("bar/value = %q, want 'world'", data2)
|
|
}
|
|
}
|
|
|
|
func TestEachDirListing(t *testing.T) {
|
|
spec := DirNode("/",
|
|
Each("{item}", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{
|
|
{Name: "alpha", Children: []FsNodeDecl{FileNode("x", 0444)}},
|
|
{Name: "beta", Children: []FsNodeDecl{FileNode("x", 0444)}},
|
|
}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
entries, err := tree.List()
|
|
if err != nil {
|
|
t.Fatalf("List: %v", err)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Fatalf("got %d entries, want 2", len(entries))
|
|
}
|
|
if entries[0].Name() != "alpha" || entries[1].Name() != "beta" {
|
|
t.Errorf("entries = %v, %v", entries[0].Name(), entries[1].Name())
|
|
}
|
|
}
|
|
|
|
func TestEachWithAliases(t *testing.T) {
|
|
spec := DirNode("/",
|
|
Each("{item}", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{{
|
|
Name: "friendly",
|
|
Aliases: []string{"uuid-123"},
|
|
Children: []FsNodeDecl{
|
|
FileNode("name", 0444, Read(func() ([]byte, error) { return []byte("found"), nil })),
|
|
},
|
|
}}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
// Access by alias
|
|
f, err := tree.Open("uuid-123/name")
|
|
if err != nil {
|
|
t.Fatalf("Open by alias: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data) != "found" {
|
|
t.Errorf("data = %q", data)
|
|
}
|
|
}
|
|
|
|
func TestStatDir(t *testing.T) {
|
|
spec := DirNode("/",
|
|
Each("{item}", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{{
|
|
Name: "sub",
|
|
Children: []FsNodeDecl{FileNode("file", 0444)},
|
|
}}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
fi, err := tree.Stat("sub")
|
|
if err != nil {
|
|
t.Fatalf("Stat: %v", err)
|
|
}
|
|
if !fi.IsDir() {
|
|
t.Error("sub should be a directory")
|
|
}
|
|
}
|
|
|
|
func TestStatFile(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("readme", 0644, Read(func() ([]byte, error) { return []byte("hi"), nil })),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
fi, err := tree.Stat("readme")
|
|
if err != nil {
|
|
t.Fatalf("Stat: %v", err)
|
|
}
|
|
if fi.IsDir() {
|
|
t.Error("readme should not be a directory")
|
|
}
|
|
if fi.Mode() != 0644 {
|
|
t.Errorf("mode = %o, want 0644", fi.Mode())
|
|
}
|
|
}
|
|
|
|
func TestNestedPath(t *testing.T) {
|
|
spec := DirNode("/",
|
|
DirNode("a",
|
|
DirNode("b",
|
|
FileNode("c", 0444, Read(func() ([]byte, error) { return []byte("deep"), nil })),
|
|
),
|
|
),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
f, err := tree.Open("a/b/c")
|
|
if err != nil {
|
|
t.Fatalf("Open a/b/c: %v", err)
|
|
}
|
|
data, err := f.Read()
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(data) != "deep" {
|
|
t.Errorf("data = %q, want 'deep'", data)
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// Each with non-template name (like "peer/")
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestEachNonTemplateName(t *testing.T) {
|
|
// This tests the pattern used by peer/ in olliesrv:
|
|
// Each("peer", ...) creates a directory whose children come from Bindings
|
|
writeData := ""
|
|
spec := DirNode("/",
|
|
Each("peer", func() ([]FsNodeDecl, error) {
|
|
return []FsNodeDecl{
|
|
{Name: "alice", Mode: 0222, Write: func(data []byte) error { writeData = string(data); return nil }},
|
|
{Name: "bob", Mode: 0222, Write: func(data []byte) error { writeData = string(data); return nil }},
|
|
}, nil
|
|
}),
|
|
)
|
|
|
|
tree := BuildTree(spec)
|
|
|
|
// List root should show "peer" as a directory
|
|
entries, err := tree.List()
|
|
if err != nil {
|
|
t.Fatalf("List root: %v", err)
|
|
}
|
|
if len(entries) != 1 {
|
|
t.Fatalf("got %d root entries, want 1", len(entries))
|
|
}
|
|
if entries[0].Name() != "peer" || !entries[0].IsDir() {
|
|
t.Errorf("root entry = %v (dir=%v), want peer dir", entries[0].Name(), entries[0].IsDir())
|
|
}
|
|
|
|
// List peer/ should show alice and bob
|
|
entries, err = tree.Readdir("peer")
|
|
if err != nil {
|
|
t.Fatalf("Readdir peer: %v", err)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Fatalf("got %d peer entries, want 2", len(entries))
|
|
}
|
|
names := map[string]bool{}
|
|
for _, e := range entries {
|
|
names[e.Name()] = true
|
|
}
|
|
if !names["alice"] || !names["bob"] {
|
|
t.Errorf("peer entries = %v, want alice and bob", entries)
|
|
}
|
|
|
|
// Open and write to peer/alice
|
|
f, err := tree.Open("peer/alice")
|
|
if err != nil {
|
|
t.Fatalf("Open peer/alice: %v", err)
|
|
}
|
|
if err := f.Write([]byte("hello")); err != nil {
|
|
t.Fatalf("Write: %v", err)
|
|
}
|
|
if writeData != "hello" {
|
|
t.Errorf("writeData = %q, want hello", writeData)
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// GenerateHelp test
|
|
// ══════════════════════════════════════════════════════════════════
|
|
|
|
func TestGenerateHelp(t *testing.T) {
|
|
spec := DirNode("/",
|
|
FileNode("ctl", 0222, Doc("Control file")),
|
|
FileNode("info", 0444, Doc("Server info")),
|
|
)
|
|
help := GenerateHelp(spec)
|
|
if help == "" {
|
|
t.Error("help is empty")
|
|
}
|
|
if !contains(help, "ctl") || !contains(help, "Control file") {
|
|
t.Errorf("help missing ctl: %q", help)
|
|
}
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
return len(s) >= len(sub) && (s == sub || len(s) > 0 && containsStr(s, sub))
|
|
}
|
|
|
|
func containsStr(s, sub string) bool {
|
|
for i := 0; i <= len(s)-len(sub); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|