port playground/test.sh to v23tests.
Change-Id: Ia994eeba356ce7eede7f207544e41871ae3279d9
diff --git a/go/src/playground/builder/main.go b/go/src/playground/builder/main.go
index 16b8c6d..a525533 100644
--- a/go/src/playground/builder/main.go
+++ b/go/src/playground/builder/main.go
@@ -32,6 +32,7 @@
"syscall"
"time"
+ vexec "v.io/core/veyron/lib/exec/consts"
"v.io/core/veyron/lib/flags/consts"
"playground/lib"
@@ -46,6 +47,8 @@
includeV23Env = flag.Bool("includeV23Env", false, "Whether to log the output of \"v23 env\" before compilation.")
// TODO(ivanpi): Separate out mounttable, proxy, wspr timeouts. Add compile timeout. Revise default.
+ // TODO(ivanpi): you can make this a time.Duration, implement the flag.Value
+ // interface as per veyron2/config settings.
runTimeout = flag.Int64("runTimeout", 3000, "Time limit for running user code, in milliseconds.")
// Sink for writing events (debug and run output) to stdout as JSON, one event per line.
@@ -410,6 +413,10 @@
func main() {
flag.Parse()
+ // TODO(cnicolaou): remove this when the isse below is resolved:
+ // https://github.com/veyron/release-issues/issues/1157
+ os.Setenv(vexec.ExecVersionVariable, "")
+
out = event.NewJsonSink(os.Stdout, !*verbose)
r, err := parseRequest(os.Stdin)
@@ -436,6 +443,5 @@
panicOnError(out.Write(event.New("<compile>", "stderr", "Compilation error.")))
return
}
-
runFiles(r.Files)
}
diff --git a/go/src/playground/builder/services.go b/go/src/playground/builder/services.go
index 6904a6d..00dc9a6 100644
--- a/go/src/playground/builder/services.go
+++ b/go/src/playground/builder/services.go
@@ -8,13 +8,11 @@
"bufio"
"fmt"
"io"
- "math/rand"
"os"
"os/exec"
"path"
"regexp"
"strconv"
- "syscall"
"time"
"v.io/core/veyron/lib/flags/consts"
@@ -26,35 +24,15 @@
proxyName = "proxy"
)
-// Note: This was copied from release/go/src/v.io/core/veyron/tools/findunusedport.
-// I would like to be able to import that package directly, but it defines a
-// main(), so can't be imported. An alternative solution would be to call the
-// 'findunusedport' binary, but that would require starting another process and
-// parsing the output. It seemed simpler to just copy the function here.
-func findUnusedPort() (int, error) {
- rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
- for i := 0; i < 1000; i++ {
- port := 1024 + rnd.Int31n(64512)
- fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
- if err != nil {
- continue
- }
- sa := &syscall.SockaddrInet4{Port: int(port)}
- if err := syscall.Bind(fd, sa); err != nil {
- continue
- }
- syscall.Close(fd)
- return int(port), nil
- }
- return 0, fmt.Errorf("Can't find unused port.")
-}
+// TODO(ivanpi): this is all implemented in veyron/lib/modules/core, you
+// may be able to use that directly.
// startMount starts a mounttabled process, and sets the NAMESPACE_ROOT env
// variable to the mounttable's location. We run one mounttabled process for
// the entire environment.
func startMount(timeLimit time.Duration) (proc *os.Process, err error) {
cmd := makeCmd("<mounttabled>", true, "mounttabled", "-veyron.tcp.address=127.0.0.1:0")
- matches, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("Mount table .+ endpoint: (.+)\n"))
+ matches, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)"))
if err != nil {
return nil, fmt.Errorf("Error starting mounttabled: %v", err)
}
@@ -70,12 +48,11 @@
func startProxy(timeLimit time.Duration) (proc *os.Process, err error) {
cmd := makeCmd("<proxyd>", true,
"proxyd",
- // Verbose logging so we can watch the output for "Proxy listening" log line.
- "-v=1",
+ "-log_dir=/tmp/logs",
"-name="+proxyName,
"-address=127.0.0.1:0",
"-http=")
- if _, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("Proxy listening")); err != nil {
+ if _, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)")); err != nil {
return nil, fmt.Errorf("Error starting proxy: %v", err)
}
return cmd.Process, nil
@@ -84,17 +61,11 @@
// startWspr starts a wsprd process. We run one wsprd process for each
// javascript file being run.
func startWspr(fileName, credentials string, timeLimit time.Duration) (proc *os.Process, port int, err error) {
- port, err = findUnusedPort()
- if err != nil {
- return nil, port, err
- }
cmd := makeCmd("<wsprd>:"+fileName, true,
"wsprd",
- // Verbose logging so we can watch the output for "Listening" log line.
- "-v=1",
"-veyron.proxy="+proxyName,
"-veyron.tcp.address=127.0.0.1:0",
- "-port="+strconv.Itoa(port),
+ "-port=0",
// Retry RPC calls for 1 second. If a client makes an RPC call before the
// server is running, it won't immediately fail, but will retry while the
// server is starting.
@@ -106,9 +77,15 @@
if credentials != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%s", consts.VeyronCredentials, path.Join("credentials", credentials)))
}
- if _, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("Listening")); err != nil {
+ parts, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile(".*port: (.*)"))
+ if err != nil {
return nil, 0, fmt.Errorf("Error starting wspr: %v", err)
}
+ portstr := parts[1]
+ port, err = strconv.Atoi(portstr)
+ if err != nil {
+ return nil, 0, fmt.Errorf("Malformed port: %q: %v", portstr, err)
+ }
return cmd.Process, port, nil
}
@@ -120,20 +97,16 @@
// util function.
func startAndWaitFor(cmd *exec.Cmd, timeout time.Duration, outputRegexp *regexp.Regexp) ([]string, error) {
reader, writer := io.Pipe()
- // TODO(sadovsky): Why must we listen to both stdout and stderr? We should
- // know which one produces the "Listening" log line...
cmd.Stdout.(*lib.MultiWriter).Add(writer)
- cmd.Stderr.(*lib.MultiWriter).Add(writer)
- err := cmd.Start()
- if err != nil {
+ if err := cmd.Start(); err != nil {
return nil, err
}
- buf := bufio.NewReader(reader)
- t := time.After(timeout)
ch := make(chan []string)
go (func() {
- for line, err := buf.ReadString('\n'); err == nil; line, err = buf.ReadString('\n') {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ line := scanner.Text()
if matches := outputRegexp.FindStringSubmatch(line); matches != nil {
ch <- matches
}
@@ -141,8 +114,8 @@
close(ch)
})()
select {
- case <-t:
- return nil, fmt.Errorf("Timeout starting service.")
+ case <-time.After(timeout):
+ return nil, fmt.Errorf("Timeout starting service: %v", cmd.Path)
case matches := <-ch:
return matches, nil
}
diff --git a/go/src/playground/playground_v23_test.go b/go/src/playground/playground_v23_test.go
new file mode 100644
index 0000000..854a7a3
--- /dev/null
+++ b/go/src/playground/playground_v23_test.go
@@ -0,0 +1,136 @@
+package playground_test
+
+import (
+ "os"
+ "path/filepath"
+
+ "v.io/core/veyron/lib/testutil/v23tests"
+ _ "v.io/core/veyron/profiles"
+)
+
+//go:generate v23 test generate
+
+var (
+ vanadiumRoot, playgroundRoot string
+)
+
+func init() {
+ vanadiumRoot = os.Getenv("VANADIUM_ROOT")
+ if len(vanadiumRoot) == 0 {
+ panic("VANADIUM_ROOT must be set")
+ }
+}
+
+func golist(i *v23tests.T, pkg string) string {
+ v23 := filepath.Join(vanadiumRoot, "bin/v23")
+ return i.Run(v23, "go", "list", "-f", "{{.Dir}}", pkg)
+}
+
+func npmLink(i *v23tests.T, dir, pkg string) {
+ npmBin := i.BinaryFromPath(filepath.Join(vanadiumRoot, "environment/cout/node/bin/npm"))
+ i.Pushd(dir)
+ npmBin.Run("link")
+ i.Popd()
+ npmBin.Run("link", pkg)
+}
+
+// Bundles a playground example and tests it using builder.
+// - dir is the root directory of example to test
+// - args are the arguments to call builder with
+func runPGExample(i *v23tests.T, dir string, args ...string) *v23tests.Invocation {
+ i.Run("./node_modules/.bin/pgbundle", dir)
+ tmp := i.NewTempDir()
+ cwd := i.Pushd(tmp)
+ old := filepath.Join(cwd, "node_modules")
+ if err := os.Symlink(old, filepath.Join(".", filepath.Base(old))); err != nil {
+ i.Fatalf("%s: symlink: failed: %v", i.Caller(2), err)
+ }
+ bundleName := filepath.Join(dir, "bundle.json")
+
+ stdin, err := os.Open(bundleName)
+ if err != nil {
+ i.Fatalf("%s: open(%s) failed: %v", i.Caller(2), bundleName, err)
+ }
+ // TODO(ivanpi): move this out so it only gets invoked once even though
+ // the binary is cached.
+ builderBin := i.BuildGoPkg("playground/builder")
+
+ PATH := "PATH=" + i.BinDir()
+ if path := os.Getenv("PATH"); len(path) > 0 {
+ PATH += ":" + path
+ }
+ defer i.Popd()
+ return builderBin.WithEnv(PATH).WithStdin(stdin).Start(args...)
+}
+
+// Sets up a directory with the given files, then runs builder.
+func testWithFiles(i *v23tests.T, pgRoot string, files ...string) *v23tests.Invocation {
+ testdataDir := filepath.Join(pgRoot, "testdata")
+ pgBundleDir := i.NewTempDir()
+ for _, f := range files {
+ fdir := filepath.Join(pgBundleDir, filepath.Dir(f))
+ if err := os.MkdirAll(fdir, 0755); err != nil {
+ i.Fatalf("%s: mkdir(%q): failed: %v", i.Caller(1), fdir, err)
+ }
+ i.Run("/bin/cp", filepath.Join(testdataDir, f), fdir)
+ }
+ return runPGExample(i, pgBundleDir, "-v=true", "--includeV23Env=true", "--runTimeout=5000")
+}
+
+func V23TestPlayground(i *v23tests.T) {
+ v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
+
+ i.BuildGoPkg("v.io/core/veyron/tools/principal")
+ i.BuildGoPkg("v.io/core/veyron/services/proxy/proxyd")
+ i.BuildGoPkg("v.io/core/veyron2/vdl/vdl")
+ i.BuildGoPkg("v.io/wspr/veyron/services/wsprd")
+
+ playgroundPkg := golist(i, "playground")
+ // strip last three directory components, much easier to read in
+ // errors than <path>/../../..
+ playgroundRoot = filepath.Dir(playgroundPkg)
+ playgroundRoot = filepath.Dir(playgroundRoot)
+ playgroundRoot = filepath.Dir(playgroundRoot)
+
+ npmLink(i, filepath.Join(vanadiumRoot, "release/javascript/core"), "veyron")
+ npmLink(i, filepath.Join(playgroundRoot, "pgbundle"), "pgbundle")
+
+ cases := []struct {
+ name string
+ files []string
+ }{
+ {"basic ping (go -> go)",
+ []string{"src/pong/pong.go", "src/ping/ping.go", "src/pingpong/wire.vdl"}},
+ {"basic ping (js -> js)",
+ []string{"src/pong/pong.js", "src/ping/ping.js", "src/pingpong/wire.vdl"}},
+ {"basic ping (js -> go)",
+ []string{"src/pong/pong.go", "src/ping/ping.js", "src/pingpong/wire.vdl"}},
+ {"basic ping (go -> js)",
+ []string{"src/pong/pong.js", "src/ping/ping.go", "src/pingpong/wire.vdl"}},
+ }
+
+ runCases := func(authfile string, patterns []string) {
+ for _, c := range cases {
+ files := c.files
+ if len(authfile) > 0 {
+ files = append(files, authfile)
+ }
+ inv := testWithFiles(i, playgroundPkg, files...)
+ i.Logf("test: %s", c.name)
+ inv.ExpectSetEventuallyRE(patterns...)
+ }
+ }
+
+ i.Logf("Test as the same principal")
+ runCases("", []string{"PING", "PONG"})
+
+ i.Logf("Test with authorized blessings")
+ runCases("src/ids/authorized.id", []string{"PING", "PONG"})
+
+ i.Logf("Test with expired blessings")
+ runCases("src/ids/expired.id", []string{"not authorized"})
+
+ i.Logf("Test with unauthorized blessings")
+ runCases("src/ids/unauthorized.id", []string{"not authorized"})
+
+}
diff --git a/go/src/playground/v23_test.go b/go/src/playground/v23_test.go
new file mode 100644
index 0000000..789ef1a
--- /dev/null
+++ b/go/src/playground/v23_test.go
@@ -0,0 +1,21 @@
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+package playground_test
+
+import "testing"
+import "os"
+
+import "v.io/core/veyron/lib/testutil"
+import "v.io/core/veyron/lib/testutil/v23tests"
+
+func TestMain(m *testing.M) {
+ testutil.Init()
+ cleanup := v23tests.UseSharedBinDir()
+ r := m.Run()
+ cleanup()
+ os.Exit(r)
+}
+
+func TestV23Playground(t *testing.T) {
+ v23tests.RunTest(t, V23TestPlayground)
+}