| // 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 |
| } |