blob: b2abff961e0aafc88977fbf8cab788fec3491600 [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 main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/x/lib/gosh"
"v.io/x/lib/set"
"v.io/x/ref"
"v.io/x/ref/lib/signals"
_ "v.io/x/ref/runtime/factories/roaming"
"v.io/x/ref/services/identity/identitylib"
"v.io/x/ref/services/mounttable/mounttablelib"
"v.io/x/ref/services/xproxy/xproxy"
"v.io/x/ref/test/expect"
"v.io/x/ref/test/v23test"
)
// NOTE(sadovsky): It appears that a lot of this code has been copied from
// v.io/x/ref/cmd/servicerunner/main.go.
var (
runTestsWatch bool
)
func init() {
flag.BoolVar(&runTestsWatch, "runTestsWatch", false, "if true runs the tests in watch mode")
}
// TODO(sadovsky): Switch to using v23test.Shell.StartRootMountTable.
var runMT = gosh.RegisterFunc("runMT", func(mp string) error {
ctx, shutdown := v23.Init()
defer shutdown()
mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
if err != nil {
return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
}
_, server, err := v23.WithNewDispatchingServer(ctx, mp, mt, options.ServesMountTable(true))
if err != nil {
return fmt.Errorf("root failed: %v", err)
}
fmt.Printf("PID=%d\n", os.Getpid())
for _, ep := range server.Status().Endpoints {
fmt.Printf("MT_NAME=%s\n", ep.Name())
}
<-signals.ShutdownOnSignals(ctx)
return nil
})
// Helper function to simply print an error and then exit.
func exitOnError(err error, desc string) {
if err != nil {
fmt.Fprintln(os.Stderr, desc, err)
os.Exit(1)
}
}
// updateVars captures the vars from the given session and adds them to the
// given vars map, overwriting existing entries.
// TODO(sadovsky): Switch to gosh.SendVars/AwaitVars.
func updateVars(s *expect.Session, vars map[string]string, varNames ...string) error {
varsToAdd := set.StringBool.FromSlice(varNames)
numLeft := len(varsToAdd)
for {
l := s.ReadLine()
if err := s.OriginalError(); err != nil {
return err // EOF or otherwise
}
parts := strings.Split(l, "=")
if len(parts) != 2 {
return fmt.Errorf("Unexpected line: %s", l)
}
if varsToAdd[parts[0]] {
numLeft--
vars[parts[0]] = parts[1]
if numLeft == 0 {
break
}
}
}
return nil
}
func main() {
gosh.InitMain()
// If we ever get a SIGHUP (terminal closes), then end the program.
signalChannel := make(chan os.Signal)
signal.Notify(signalChannel, syscall.SIGHUP)
go func() {
sig := <-signalChannel
switch sig {
case syscall.SIGHUP:
os.Exit(1)
}
}()
// Try running the program; on failure, exit with error status code.
if !run() {
os.Exit(1)
}
}
// Runs the services and cleans up afterwards.
// Returns true if the run was successful.
func run() bool {
// In order to prevent conflicts, tests and webapp use different mounttable
// ports.
port := 8884
cottagePort := 8885
housePort := 8886
host := "localhost"
err := os.MkdirAll("tmp/runner", 0750)
exitOnError(err, "Could not make temp directory")
vars := map[string]string{}
sh := v23test.NewShell(nil, nil)
defer sh.Cleanup()
sh.ChildOutputDir = "tmp/runner"
ctx := sh.Ctx
// Run a mounttable for tests
cRoot := sh.FuncCmd(runMT, "root")
cRoot.Args = append(cRoot.Args, "--v23.tcp.protocol=wsh", fmt.Sprintf("--v23.tcp.address=%s:%d", host, port))
cRoot.Start()
exitOnError(err, "Failed to start root mount table")
exitOnError(updateVars(cRoot.S, vars, "MT_NAME"), "Failed to get MT_NAME")
// Set ref.EnvNamespacePrefix env var, consumed downstream.
sh.Vars[ref.EnvNamespacePrefix] = vars["MT_NAME"]
v23.GetNamespace(ctx).SetRoots(vars["MT_NAME"])
// Run the cottage mounttable at host/cottage.
cCottage := sh.FuncCmd(runMT, "cottage")
cCottage.Args = append(cCottage.Args, "--v23.tcp.protocol=wsh", fmt.Sprintf("--v23.tcp.address=%s:%d", host, cottagePort))
cCottage.Start()
exitOnError(err, "Failed to start cottage mount table")
// run the house mounttable at host/house.
cHouse := sh.FuncCmd(runMT, "house")
cHouse.Args = append(cHouse.Args, "--v23.tcp.protocol=wsh", fmt.Sprintf("--v23.tcp.address=%s:%d", host, housePort))
cHouse.Start()
exitOnError(err, "Failed to start house mount table")
// Just print out the collected variables. This is for debugging purposes.
bytes, err := json.Marshal(vars)
exitOnError(err, "Failed to marshal the collected variables")
fmt.Println(string(bytes))
// Also set HOUSE_MOUNTTABLE (used in the tests)
os.Setenv("HOUSE_MOUNTTABLE", fmt.Sprintf("/%s:%d", host, housePort))
lspec := v23.GetListenSpec(ctx)
lspec.Addrs = rpc.ListenAddrs{{"wsh", ":0"}}
ctx = v23.WithListenSpec(ctx, lspec)
ctx, cancel := context.WithCancel(ctx)
proxyACL := access.AccessList{In: security.DefaultBlessingPatterns(v23.GetPrincipal(ctx))}
proxy, err := xproxy.New(ctx, "test/proxy", proxyACL)
exitOnError(err, "Failed to start proxy")
vars["PROXY_NAME"] = proxy.ListeningEndpoints()[0].Name()
defer func() {
cancel()
<-proxy.Closed()
}()
cIdentityd := sh.FuncCmd(identitylib.TestIdentityd)
cIdentityd.Args = append(cIdentityd.Args, "--v23.tcp.protocol=wsh", "--v23.tcp.address=:0", "--http-addr=localhost:0")
cIdentityd.Start()
exitOnError(err, "Failed to start identityd")
exitOnError(updateVars(cIdentityd.S, vars, "TEST_IDENTITYD_NAME", "TEST_IDENTITYD_HTTP_ADDR"), "Failed to obtain identityd address")
// Setup a lot of environment variables; these are used for the tests and building the test extension.
os.Setenv(ref.EnvNamespacePrefix, vars["MT_NAME"])
os.Setenv("PROXY_ADDR", vars["PROXY_NAME"])
os.Setenv("IDENTITYD", fmt.Sprintf("%s/google", vars["TEST_IDENTITYD_NAME"]))
os.Setenv("IDENTITYD_BLESSING_URL", fmt.Sprintf("%s/auth/blessing-root", vars["TEST_IDENTITYD_HTTP_ADDR"]))
os.Setenv("DEBUG", "false")
testsOk := runProva()
fmt.Println("Cleaning up launched services...")
return testsOk
}
// Run the prova tests and convert its tap output to xunit.
func runProva() bool {
// This is also useful information for routing the test output.
JIRI_ROOT := os.Getenv("JIRI_ROOT")
VANADIUM_JS := fmt.Sprintf("%s/release/javascript/core", JIRI_ROOT)
VANADIUM_BROWSER := fmt.Sprintf("%s/release/projects/browser", JIRI_ROOT)
TAP_XUNIT := fmt.Sprintf("%s/node_modules/.bin/tap-xunit", VANADIUM_BROWSER)
XUNIT_OUTPUT_FILE := os.Getenv("XUNIT_OUTPUT_FILE")
if XUNIT_OUTPUT_FILE == "" {
XUNIT_OUTPUT_FILE = fmt.Sprintf("%s/test_output.xml", os.Getenv("TMPDIR"))
}
TAP_XUNIT_OPTIONS := " --package=namespace-browser"
// Make sure we're in the right folder when we run make test-extension.
vbroot, err := os.Open(VANADIUM_BROWSER)
exitOnError(err, "Failed to open vanadium browser dir")
err = vbroot.Chdir()
exitOnError(err, "Failed to change to vanadium browser dir")
// Make the test-extension, this should also remove the old one.
fmt.Println("Rebuilding test extension...")
cmdExtensionClean := exec.Command("rm", "-fr", fmt.Sprintf("%s/extension/build-test", VANADIUM_JS))
err = cmdExtensionClean.Run()
exitOnError(err, "Failed to clean test extension")
cmdExtensionBuild := exec.Command("make", "-C", fmt.Sprintf("%s/extension", VANADIUM_JS), "build-test")
out, err := cmdExtensionBuild.CombinedOutput()
exitOnError(err, fmt.Sprintf("Failed to build test extension: make -C %s/extension build-test [[\n%s\n]]", VANADIUM_JS, out))
// These are the basic prova options.
options := []string{
"test/**/*.js",
"--browser",
"--includeFilenameAsPackage",
"--launch",
"chrome",
"--plugin",
"proxyquireify/plugin",
"--transform",
"envify,./main-transform",
"--log",
"tmp/chrome.log",
fmt.Sprintf("--options=--load-extension=%s/extension/build-test/,--ignore-certificate-errors,--enable-logging=stderr", VANADIUM_JS),
}
// Normal tests have a few more options and a different port from the watch tests.
var PROVA_PORT int
if !runTestsWatch {
PROVA_PORT = 8893
options = append(options, "--headless", "--quit", "--progress", "--tap")
fmt.Printf("\033[34m-Executing tests. See %s for test xunit output.\033[0m\n", XUNIT_OUTPUT_FILE)
} else {
PROVA_PORT = 8894
fmt.Println("\033[34m-Running tests in watch mode.\033[0m")
}
options = append(options, "--port", fmt.Sprintf("%d", PROVA_PORT))
// This is the prova command.
cmdProva := exec.Command(
fmt.Sprintf("%s/node_modules/.bin/prova", VANADIUM_BROWSER),
options...,
)
fmt.Printf("\033[34m-Go to \033[32mhttp://0.0.0.0:%d\033[34m to see tests running.\033[0m\n", PROVA_PORT)
fmt.Println(cmdProva)
// Collect the prova stdout. This information needs to be sent to xunit.
provaOut, err := cmdProva.StdoutPipe()
exitOnError(err, "Failed to get prova stdout pipe")
// Setup the tap to xunit command. It uses Prova's stdout as input.
// The output will got the xunit output file.
cmdTap := exec.Command(TAP_XUNIT, TAP_XUNIT_OPTIONS)
cmdTap.Stdin = io.TeeReader(provaOut, os.Stdout) // Tee the prova output to see it on the console too.
outfile, err := os.Create(XUNIT_OUTPUT_FILE)
exitOnError(err, "Failed to create xunit output file")
defer outfile.Close()
bufferedWriter := bufio.NewWriter(outfile)
cmdTap.Stdout = bufferedWriter
defer bufferedWriter.Flush() // Ensure that the full xunit output is written.
// We start the tap command...
err = cmdTap.Start()
exitOnError(err, "Failed to start tap to xunit command")
// Meanwhile, run Prova to completion. If there was an error, print ERROR, otherwise PASS.
err = cmdProva.Run()
testsOk := true
if err != nil {
fmt.Println(err)
fmt.Println("\033[31m\033[1mERROR\033[0m")
testsOk = false
} else {
fmt.Println("\033[32m\033[1mPASS\033[0m")
}
// Wait for tap to xunit to finish itself off. This file will be ready for reading by Jenkins.
fmt.Println("Converting Tap output to XUnit")
err = cmdTap.Wait()
exitOnError(err, "Failed tap to xunit conversion")
return testsOk
}