blob: b381ddad0edfd155a2f5cfb64f4c338bb3576dc5 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 test
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/glob"
"v.io/v23/i18n"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/rpc/reserved"
"v.io/v23/security"
"v.io/v23/verror"
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
func TestGlob(t *testing.T) {
ctx, shutdown := test.V23InitWithMounttable()
defer shutdown()
namespace := []string{
"a/b/c1/d1",
"a/b/c1/d2",
"a/b/c2/d1",
"a/b/c2/d2",
"a/x/y/z",
"leaf",
}
tree := newNode()
for _, p := range namespace {
tree.find(strings.Split(p, "/"), true)
}
_, server, err := v23.WithNewDispatchingServer(ctx, "", &disp{tree})
if err != nil {
t.Fatalf("failed to start debug server: %v", err)
}
ep := server.Status().Endpoints[0].String()
var (
noExist = verror.New(verror.ErrNoExist, ctx, "")
notImplemented = reserved.NewErrGlobNotImplemented(ctx)
maxRecursion = reserved.NewErrGlobMaxRecursionReached(ctx)
)
testcases := []struct {
name, pattern string
expected []string
errors []naming.GlobError
}{
{"", "...", []string{
"",
"a",
"a/b",
"a/b/c1",
"a/b/c1/d1",
"a/b/c1/d2",
"a/b/c2",
"a/b/c2/d1",
"a/b/c2/d2",
"a/x",
"a/x/y",
"a/x/y/z",
"leaf",
}, nil},
{"a", "...", []string{
"",
"b",
"b/c1",
"b/c1/d1",
"b/c1/d2",
"b/c2",
"b/c2/d1",
"b/c2/d2",
"x",
"x/y",
"x/y/z",
}, nil},
{"a/b", "...", []string{
"",
"c1",
"c1/d1",
"c1/d2",
"c2",
"c2/d1",
"c2/d2",
}, nil},
{"a/b/c1", "...", []string{
"",
"d1",
"d2",
}, nil},
{"a/b/c1/d1", "...", []string{
"",
}, nil},
{"a/x", "...", []string{
"",
"y",
"y/z",
}, nil},
{"a/x/y", "...", []string{
"",
"z",
}, nil},
{"a/x/y/z", "...", []string{
"",
}, nil},
{"", "", []string{""}, nil},
{"", "*", []string{"a", "leaf"}, nil},
{"a", "", []string{""}, nil},
{"a", "*", []string{"b", "x"}, nil},
{"a/b", "", []string{""}, nil},
{"a/b", "*", []string{"c1", "c2"}, nil},
{"a/b/c1", "", []string{""}, nil},
{"a/b/c1", "*", []string{"d1", "d2"}, nil},
{"a/b/c1/d1", "*", []string{}, nil},
{"a/b/c1/d1", "", []string{""}, nil},
{"a/b/c2", "", []string{""}, nil},
{"a/b/c2", "*", []string{"d1", "d2"}, nil},
{"a/b/c2/d1", "*", []string{}, nil},
{"a/b/c2/d1", "", []string{""}, nil},
{"a", "*/c?", []string{"b/c1", "b/c2"}, nil},
{"a", "*/*", []string{"b/c1", "b/c2", "x/y"}, nil},
{"a", "*/*/*", []string{"b/c1/d1", "b/c1/d2", "b/c2/d1", "b/c2/d2", "x/y/z"}, nil},
{"a/x", "*/*", []string{"y/z"}, nil},
{"bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"bad/foo", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"a/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"a/b/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"a/b/c1/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"a/x/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"a/x/y/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
{"leaf", "", []string{""}, nil},
{"leaf", "*", []string{}, []naming.GlobError{{Name: "", Error: notImplemented}}},
{"leaf/foo", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
// muah is an infinite space to test rescursion limit.
{"muah", "*", []string{"ha"}, nil},
{"muah", "*/*", []string{"ha/ha"}, nil},
{"muah", "*/*/*/*/*/*/*/*/*/*/*/*", []string{"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha"}, nil},
{"muah", "...", []string{
"",
"ha",
"ha/ha",
"ha/ha/ha",
"ha/ha/ha/ha",
"ha/ha/ha/ha/ha",
"ha/ha/ha/ha/ha/ha",
"ha/ha/ha/ha/ha/ha/ha",
"ha/ha/ha/ha/ha/ha/ha/ha",
"ha/ha/ha/ha/ha/ha/ha/ha/ha",
"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha",
}, []naming.GlobError{{Name: "ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha", Error: maxRecursion}}},
}
for _, tc := range testcases {
name := naming.JoinAddressName(ep, tc.name)
results, globErrors, err := testutil.GlobName(ctx, name, tc.pattern)
if err != nil {
t.Errorf("unexpected Glob error for (%q, %q): %v", tc.name, tc.pattern, err)
continue
}
if !reflect.DeepEqual(results, tc.expected) {
t.Errorf("unexpected result for (%q, %q). Got %q, want %q", tc.name, tc.pattern, results, tc.expected)
}
if len(globErrors) != len(tc.errors) {
t.Errorf("unexpected number of glob errors for (%q, %q): got %#v, expected %#v", tc.name, tc.pattern, globErrors, tc.errors)
}
for i, e := range globErrors {
if i >= len(tc.errors) {
t.Errorf("unexpected glob error for (%q, %q): %#v", tc.name, tc.pattern, e.Error)
continue
}
if e.Name != tc.errors[i].Name {
t.Errorf("unexpected glob error for (%q, %q): %v", tc.name, tc.pattern, e)
}
if got, expected := verror.ErrorID(e.Error), verror.ErrorID(tc.errors[i].Error); got != expected {
t.Errorf("unexpected error ID for (%q, %q): Got %v, expected %v", tc.name, tc.pattern, got, expected)
}
}
}
}
func TestGlobDeny(t *testing.T) {
ctx, shutdown := test.V23InitWithMounttable()
defer shutdown()
tree := newNode()
tree.find([]string{"a", "b"}, true)
tree.find([]string{"a", "deny", "x"}, true)
_, server, err := v23.WithNewDispatchingServer(ctx, "", &disp{tree})
if err != nil {
t.Fatalf("failed to start debug server: %v", err)
}
ep := server.Status().Endpoints[0].String()
testcases := []struct {
name, pattern string
results []string
numErrors int
}{
{"", "*", []string{"a"}, 0},
{"", "*/*", []string{"a/b"}, 1},
{"a", "*", []string{"b"}, 1},
{"a/deny", "*", []string{}, 1},
{"a/deny/x", "*", []string{}, 1},
{"a/deny/y", "*", []string{}, 1},
}
// Ensure that we're getting the english error message.
ctx = i18n.WithLangID(ctx, i18n.LangID("en-US"))
for _, tc := range testcases {
name := naming.JoinAddressName(ep, tc.name)
results, globErrors, err := testutil.GlobName(ctx, name, tc.pattern)
if err != nil {
t.Errorf("unexpected Glob error for (%q, %q): %v", tc.name, tc.pattern, err)
}
if !reflect.DeepEqual(results, tc.results) {
t.Errorf("unexpected results for (%q, %q). Got %v, expected %v", tc.name, tc.pattern, results, tc.results)
}
if len(globErrors) != tc.numErrors {
t.Errorf("unexpected number of errors for (%q, %q). Got %v, expected %v", tc.name, tc.pattern, len(globErrors), tc.numErrors)
}
for _, gerr := range globErrors {
if got, expected := verror.ErrorID(gerr.Error), reserved.ErrGlobMatchesOmitted.ID; got != expected {
t.Errorf("unexpected error for (%q, %q): Got %v, expected %v", tc.name, tc.pattern, got, expected)
}
// This error message is purposely vague to avoid leaking information that
// the user doesn't have access to.
// We check the actual error string to make sure that we don't start
// leaking new information by accident.
expectedStr := fmt.Sprintf(
`test.test:"%s".__Glob: some matches might have been omitted`,
tc.name)
if got := gerr.Error.Error(); got != expectedStr {
t.Errorf("unexpected error string: Got %q, expected %q", got, expectedStr)
}
}
}
}
type disp struct {
tree *node
}
func (d *disp) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
elems := strings.Split(suffix, "/")
var auth security.Authorizer
for _, e := range elems {
if e == "deny" {
return &leafObject{}, &denyAllAuthorizer{}, nil
}
}
if len(elems) != 0 && elems[0] == "muah" {
// Infinite space. Each node has one child named "ha".
return rpc.ChildrenGlobberInvoker("ha"), auth, nil
}
if len(elems) != 0 && elems[len(elems)-1] == "leaf" {
return &leafObject{}, auth, nil
}
n := d.tree.find(elems, false)
if n == nil {
return nil, nil, verror.New(verror.ErrNoExist, ctx, suffix)
}
if len(elems) < 2 || (elems[0] == "a" && elems[1] == "x") {
return &vChildrenObject{n}, auth, nil
}
return &globObject{n}, auth, nil
}
type denyAllAuthorizer struct{}
func (denyAllAuthorizer) Authorize(*context.T, security.Call) error {
return errors.New("no access")
}
type globObject struct {
n *node
}
func (o *globObject) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
o.globLoop(call, "", g, o.n)
return nil
}
func (o *globObject) globLoop(call rpc.GlobServerCall, name string, g *glob.Glob, n *node) {
if g.Len() == 0 {
call.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: name}})
}
if g.Empty() {
return
}
matcher, left := g.Head(), g.Tail()
for leaf, child := range n.children {
if matcher.Match(leaf) {
o.globLoop(call, naming.Join(name, leaf), left, child)
}
}
}
type vChildrenObject struct {
n *node
}
func (o *vChildrenObject) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
sender := call.SendStream()
for child, _ := range o.n.children {
if m.Match(child) {
sender.Send(naming.GlobChildrenReplyName{Value: child})
}
}
return nil
}
type node struct {
children map[string]*node
}
func newNode() *node {
return &node{make(map[string]*node)}
}
func (n *node) find(names []string, create bool) *node {
if len(names) == 1 && names[0] == "" {
return n
}
for {
if len(names) == 0 {
return n
}
if next, ok := n.children[names[0]]; ok {
n = next
names = names[1:]
continue
}
if create {
nn := newNode()
n.children[names[0]] = nn
n = nn
names = names[1:]
continue
}
return nil
}
}
type leafObject struct{}
func (leafObject) Func(*context.T, rpc.ServerCall) error {
return nil
}