veyron/tools/naming/simulator: simple interprerter for building
different veyron service configurations.
Change-Id: I6178008cf125ff7224978d444e96b30e6b1b6894
diff --git a/lib/testutil/modules/ls.go b/lib/testutil/modules/ls.go
index f94d277..daac425 100644
--- a/lib/testutil/modules/ls.go
+++ b/lib/testutil/modules/ls.go
@@ -91,7 +91,6 @@
for e := range ch {
reply = append(reply, fmt.Sprintf("%q", e.Name))
}
- reply = append(reply, fmt.Sprintf("%d: items", len(reply)))
return reply, nil
}
diff --git a/lib/testutil/modules/servers.go b/lib/testutil/modules/servers.go
index f21e508..9d93685 100644
--- a/lib/testutil/modules/servers.go
+++ b/lib/testutil/modules/servers.go
@@ -3,6 +3,7 @@
import (
"fmt"
"os"
+ "strings"
"time"
"veyron/lib/testutil/blackbox"
@@ -122,7 +123,7 @@
return nil, nil, nil, err
}
v := make(Variables)
- v.Update("TIME", r)
+ v.Update("TIME", strings.TrimRight(r, "\n"))
return v, []string{r}, nil, nil
}
@@ -169,7 +170,7 @@
return nil, nil, nil, err
}
v := make(Variables)
- v.Update("ECHO", r)
+ v.Update("ECHO", strings.TrimRight(r, "\n"))
return v, []string{r}, nil, nil
}
diff --git a/tools/naming/simulator/commands.go b/tools/naming/simulator/commands.go
new file mode 100644
index 0000000..937990c
--- /dev/null
+++ b/tools/naming/simulator/commands.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "veyron/lib/testutil/modules"
+
+ "veyron2/vlog"
+)
+
+type tag int
+
+const (
+ helpTag tag = iota
+ getTag
+ setTag
+ printTag
+ sleepTag
+)
+
+type builtin struct{ tag }
+
+func helpF() modules.T {
+ return &builtin{helpTag}
+}
+
+func sleepF() modules.T {
+ return &builtin{sleepTag}
+}
+
+func getF() modules.T {
+ return &builtin{getTag}
+}
+
+func setF() modules.T {
+ return &builtin{setTag}
+}
+
+func printF() modules.T {
+ return &builtin{printTag}
+}
+
+func (b *builtin) Help() string {
+ switch b.tag {
+ case helpTag:
+ return "[command]"
+ case sleepTag:
+ return `[duration]
+ sleep for a time (in go time.Duration format): defaults to 1s`
+ case getTag:
+ return "[<global variable name>]*"
+ case setTag:
+ return "[<var>=<val>]+"
+ case printTag:
+ return "[$<var>]*"
+ default:
+ return fmt.Sprintf("unrecognised tag for builtin: %d", b.tag)
+ }
+}
+
+func (*builtin) Daemon() bool { return false }
+
+func (b *builtin) Run(args []string) (modules.Variables, []string, modules.Handle, error) {
+ switch b.tag {
+ case helpTag:
+ return helpCmd(args)
+ case sleepTag:
+ return sleep(args)
+ case getTag:
+ return get(args)
+ case setTag:
+ return set(args)
+ case printTag:
+ return print(args)
+ default:
+ return nil, nil, nil, fmt.Errorf("unrecognised tag for builtin: %d",
+ b.tag)
+ }
+}
+
+func helpCmd([]string) (modules.Variables, []string, modules.Handle, error) {
+ for k, v := range commands {
+ if k == "help" {
+ continue
+ }
+ h := v().Help()
+ if len(h) > 0 {
+ fmt.Printf("%s %s\n\n", k, h)
+ } else {
+ fmt.Println(k)
+ }
+ }
+ return nil, nil, nil, nil
+}
+
+func sleep(args []string) (modules.Variables, []string, modules.Handle, error) {
+ if len(args) == 0 {
+ vlog.Infof("Sleeping for %s", time.Second)
+ time.Sleep(time.Second)
+ return nil, nil, nil, nil
+ }
+ if d, err := time.ParseDuration(args[0]); err != nil {
+ return nil, nil, nil, err
+ } else {
+ vlog.Infof("Sleeping for %s", d)
+ time.Sleep(d)
+ }
+ return nil, nil, nil, nil
+}
+
+func get(args []string) (modules.Variables, []string, modules.Handle, error) {
+ var r []string
+ if len(args) == 0 {
+ for k, v := range globals {
+ r = append(r, fmt.Sprintf("\t%q=%q\n", k, v))
+ }
+ } else {
+ for _, a := range args {
+ if v, present := globals[a]; present {
+ r = append(r, fmt.Sprintf("\t%q=%q\n", a, v))
+ } else {
+ return nil, nil, nil, fmt.Errorf("unknown variable %q", a)
+ }
+ }
+ }
+ return nil, r, nil, nil
+}
+
+func set(args []string) (modules.Variables, []string, modules.Handle, error) {
+ for _, a := range args {
+ globals.UpdateFromString(a)
+ }
+ return nil, nil, nil, nil
+}
+
+func print(args []string) (modules.Variables, []string, modules.Handle, error) {
+ var r []string
+ for _, a := range args {
+ r = append(r, a)
+ }
+ return nil, r, nil, nil
+}
diff --git a/tools/naming/simulator/driver.go b/tools/naming/simulator/driver.go
new file mode 100644
index 0000000..4a48737
--- /dev/null
+++ b/tools/naming/simulator/driver.go
@@ -0,0 +1,242 @@
+// This app provides a simple scripted environment for running common veyron
+// services as subprocesses and testing interactions between them. It is
+// structured as an interpreter, with global variables and variable
+// expansion, but no control flow. The command set that it supports is
+// extendable by adding new 'modules' that implement the API defined
+// by veyron/lib/testutil/modules.
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "unicode"
+
+ "veyron/lib/testutil/modules"
+
+ "veyron2/rt"
+)
+
+type commandFunc func() modules.T
+
+var (
+ commands map[string]commandFunc
+ globals modules.Variables
+ debug bool
+ interactive bool
+)
+
+func init() {
+ flag.BoolVar(&interactive, "interactive", true, "set interactive/batch mode")
+ flag.BoolVar(&debug, "debug", false, "set debug mode")
+
+ commands = make(map[string]commandFunc)
+
+ // We maintaing a single, global, dictionary for variables.
+ globals = make(modules.Variables)
+
+ // 'bultins'
+ commands["help"] = helpF
+ commands["get"] = getF
+ commands["set"] = setF
+ commands["print"] = printF
+ commands["sleep"] = sleepF
+
+ // TODO(cnicolaou): add 'STOP' command to shutdown a running server,
+ // need to return the handle and then call Stop on it.
+
+ // modules
+ commands["rootMT"] = modules.NewRootMT
+ commands["nodeMT"] = modules.NewNodeMT
+ commands["setLocalRoots"] = modules.NewSetRoot
+ commands["ls"] = modules.NewGlob
+ commands["lsat"] = modules.NewGlobAt
+ commands["lsmt"] = modules.NewGlobAtMT
+ commands["resolve"] = modules.NewResolve
+ commands["resolveMT"] = modules.NewResolveMT
+ commands["echoServer"] = modules.NewEchoServer
+ commands["echo"] = modules.NewEchoClient
+ commands["clockServer"] = modules.NewClockServer
+ commands["time"] = modules.NewClockClient
+}
+
+func prompt(lineno int) {
+ if interactive {
+ fmt.Printf("%d> ", lineno)
+ }
+}
+
+func main() {
+ modules.InModule()
+ rt.Init()
+
+ scanner := bufio.NewScanner(os.Stdin)
+ lineno := 1
+ prompt(lineno)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if !strings.HasPrefix(line, "#") && len(line) > 0 {
+ if err := process(line, lineno); err != nil {
+ if debug {
+ fmt.Printf("%d> %s: %v\n", lineno, line, err)
+ } else {
+ fmt.Printf("%d> %v\n", lineno, err)
+ }
+ }
+ }
+ lineno++
+ prompt(lineno)
+ }
+ if err := scanner.Err(); err != nil {
+ fmt.Printf("error reading input: %v\n", err)
+ }
+
+ modules.Cleanup()
+}
+
+func process(line string, lineno int) error {
+ fields, err := splitQuotedFields(line)
+ if err != nil {
+ return err
+ }
+ if len(fields) == 0 {
+ return fmt.Errorf("no input")
+ }
+ name := fields[0]
+
+ var args []string
+ if len(fields) > 1 {
+ args = fields[1:]
+ } else {
+ args = []string{}
+ }
+
+ sub, err := subVariables(args, globals)
+ if err != nil {
+ return err
+ }
+
+ factory := commands[name]
+ if factory == nil {
+ return fmt.Errorf("unrecognised command %q", name)
+ }
+
+ if vars, output, _, err := factory().Run(sub); err != nil {
+ return err
+ } else {
+ if debug || interactive {
+ if !interactive {
+ fmt.Printf("%d> %s\n", lineno, line)
+ }
+ if len(output) > 0 {
+ fmt.Printf("%s\n", strings.Join(output, " "))
+ }
+ }
+ if debug && len(vars) > 0 {
+ for k, v := range vars {
+ fmt.Printf("\t%s=%q .... \n", k, v)
+ }
+ fmt.Println()
+ }
+ globals.UpdateFromVariables(vars)
+ }
+ return nil
+}
+
+// splitQuotedFields a line into fields, allowing for quoted strings.
+func splitQuotedFields(line string) ([]string, error) {
+ fields := []string{}
+ inquote := false
+ var field []rune
+ for _, c := range line {
+ switch {
+ case c == '"':
+ if inquote {
+ fields = append(fields, string(field))
+ field = nil
+ inquote = false
+ } else {
+ inquote = true
+ }
+ case unicode.IsSpace(c):
+ if inquote {
+ field = append(field, c)
+ } else {
+ if len(field) > 0 {
+ fields = append(fields, string(field))
+ }
+ field = nil
+ }
+ default:
+ field = append(field, c)
+ }
+ }
+ if inquote {
+ return nil, fmt.Errorf("unterminated quoted input")
+ }
+
+ if len(field) > 0 {
+ fields = append(fields, string(field))
+ }
+ return fields, nil
+}
+
+// subVariables substitutes variables that occur in the string slice
+// args with values from vars.
+func subVariables(args []string, vars modules.Variables) ([]string, error) {
+ var results []string
+ for _, a := range args {
+ if r, err := subVariablesInArgument(a, vars); err != nil {
+ return results, err
+ } else {
+ results = append(results, r)
+ }
+ }
+ return results, nil
+}
+
+// subVariablesInArgument substitutes variables that occur in the string
+// parameter with values from vars.
+//
+// A variable, is introduced by $, terminated by \t, space, / , : or !.
+// Variables may also be enclosed by {} (as in ${VAR}) to allow for embedding
+// within strings.
+func subVariablesInArgument(a string, vars modules.Variables) (string, error) {
+ first := strings.Index(a, "$")
+ if first < 0 {
+ return a, nil
+ }
+ parts := strings.Split(a, "$")
+ result := parts[0]
+ vn := ""
+ rem := 0
+ for _, p := range parts[1:] {
+ start := 0
+ end := -1
+ if strings.HasPrefix(p, "{") {
+ start = 1
+ end = strings.Index(p, "}")
+ if end < 0 {
+ return "", fmt.Errorf("unterminated variable: %q", p)
+ }
+ rem = end + 1
+ } else {
+ end = strings.IndexAny(p, "\t/,:! ")
+ if end < 0 {
+ end = len(p)
+ }
+ rem = end
+ }
+ vn = p[start:end]
+ r := p[rem:]
+ v, present := vars[vn]
+ if !present {
+ return "", fmt.Errorf("unknown variable: %q", vn)
+ }
+ result += v
+ result += r
+ }
+ return result, nil
+}
diff --git a/tools/naming/simulator/driver_test.go b/tools/naming/simulator/driver_test.go
new file mode 100644
index 0000000..ed6f168
--- /dev/null
+++ b/tools/naming/simulator/driver_test.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func TestFields(t *testing.T) {
+ cases := []struct {
+ input string
+ output []string
+ }{
+ {"", []string{}},
+ {"a", []string{"a"}},
+ {" z", []string{"z"}},
+ {" zz zz", []string{"zz", "zz"}},
+ {"ab", []string{"ab"}},
+ {"a b", []string{"a", "b"}},
+ {`a " b"`, []string{"a", " b"}},
+ {`a " b zz"`, []string{"a", " b zz"}},
+ {`a " b zz"`, []string{"a", " b zz"}},
+ {`a " b" cc`, []string{"a", " b", "cc"}},
+ {`a "z b" cc`, []string{"a", "z b", "cc"}},
+ }
+ for i, c := range cases {
+ got, err := splitQuotedFields(c.input)
+ if err != nil {
+ t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+ }
+ if !reflect.DeepEqual(got, c.output) {
+ t.Errorf("%d: %q: got %#v, want %#v", i, c.input, got, c.output)
+ }
+ }
+ if _, err := splitQuotedFields(`a b "c`); err == nil {
+ t.Errorf("expected error for unterminated quote")
+ }
+}
+
+func TestVariables(t *testing.T) {
+ globals["foo"] = "bar"
+ cases := []struct {
+ input string
+ output []string
+ }{
+ {"a b", []string{"a", "b"}},
+ {"a $foo", []string{"a", "bar"}},
+ {"$foo a", []string{"bar", "a"}},
+ {`a "$foo "`, []string{"a", "bar "}},
+ {"a xx$foo", []string{"a", "xxbar"}},
+ {"a xx${foo}yy", []string{"a", "xxbaryy"}},
+ {`a "foo"`, []string{"a", "foo"}},
+ }
+ for i, c := range cases {
+ fields, err := splitQuotedFields(c.input)
+ if err != nil {
+ t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+ }
+ got, err := subVariables(fields, globals)
+ if err != nil {
+ t.Errorf("%d: %q: unexpected error: %v", i, c.input, err)
+ }
+ if !reflect.DeepEqual(got, c.output) {
+ t.Errorf("%d: %q: got %#v, want %#v", i, c.input, got, c.output)
+ }
+ }
+
+ errors := []struct {
+ input string
+ err error
+ }{
+ {"$foox", fmt.Errorf("unknown variable: %q", "foox")},
+ {"${fo", fmt.Errorf("unterminated variable: %q", "{fo")},
+ }
+ for i, c := range errors {
+ _, got := subVariables([]string{c.input}, globals)
+ if got.Error() != c.err.Error() {
+ t.Errorf("%d: %q: expected error: got %q, want %q", i, c.input, got, c.err)
+ }
+ }
+}