Roboleaf product configuration runner

The application rbcrun executes Starlark scripts that define Android product configurations.
See README.md for details.

Test: go test
Fixes: 180529448
Change-Id: I7d728b47d3f381b7052a0d7d51c9e698e5c2e316
diff --git a/tools/rbcrun/host_test.go b/tools/rbcrun/host_test.go
new file mode 100644
index 0000000..3be5ee6
--- /dev/null
+++ b/tools/rbcrun/host_test.go
@@ -0,0 +1,159 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rbcrun
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"testing"
+
+	"go.starlark.net/resolve"
+	"go.starlark.net/starlark"
+	"go.starlark.net/starlarktest"
+)
+
+// In order to use "assert.star" from go/starlark.net/starlarktest in the tests,
+// provide:
+//  * load function that handles "assert.star"
+//  * starlarktest.DataFile function that finds its location
+
+func init() {
+	starlarktestSetup()
+}
+
+func starlarktestSetup() {
+	resolve.AllowLambda = true
+	starlarktest.DataFile = func(pkgdir, filename string) string {
+		// The caller expects this function to return the path to the
+		// data file. The implementation assumes that the source file
+		// containing the caller and the data file are in the same
+		// directory. It's ugly. Not sure what's the better way.
+		// TODO(asmundak): handle Bazel case
+		_, starlarktestSrcFile, _, _ := runtime.Caller(1)
+		if filepath.Base(starlarktestSrcFile) != "starlarktest.go" {
+			panic(fmt.Errorf("this function should be called from starlarktest.go, got %s",
+				starlarktestSrcFile))
+		}
+		return filepath.Join(filepath.Dir(starlarktestSrcFile), filename)
+	}
+}
+
+// Common setup for the tests: create thread, change to the test directory
+func testSetup(t *testing.T, env []string) *starlark.Thread {
+	setup(env)
+	thread := &starlark.Thread{
+		Load: func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
+			if module == "assert.star" {
+				return starlarktest.LoadAssertModule()
+			}
+			return nil, fmt.Errorf("load not implemented")
+		}}
+	starlarktest.SetReporter(thread, t)
+	if err := os.Chdir(dataDir()); err != nil {
+		t.Fatal(err)
+	}
+	return thread
+}
+
+func dataDir() string {
+	_, thisSrcFile, _, _ := runtime.Caller(0)
+	return filepath.Join(filepath.Dir(thisSrcFile), "testdata")
+
+}
+
+func exerciseStarlarkTestFile(t *testing.T, starFile string) {
+	// In order to use "assert.star" from go/starlark.net/starlarktest in the tests, provide:
+	//  * load function that handles "assert.star"
+	//  * starlarktest.DataFile function that finds its location
+	setup(nil)
+	thread := &starlark.Thread{
+		Load: func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
+			if module == "assert.star" {
+				return starlarktest.LoadAssertModule()
+			}
+			return nil, fmt.Errorf("load not implemented")
+		}}
+	starlarktest.SetReporter(thread, t)
+	_, thisSrcFile, _, _ := runtime.Caller(0)
+	filename := filepath.Join(filepath.Dir(thisSrcFile), starFile)
+	if _, err := starlark.ExecFile(thread, filename, nil, builtins); err != nil {
+		if err, ok := err.(*starlark.EvalError); ok {
+			t.Fatal(err.Backtrace())
+		}
+		t.Fatal(err)
+	}
+}
+
+func TestCliAndEnv(t *testing.T) {
+	// TODO(asmundak): convert this to use exerciseStarlarkTestFile
+	if err := os.Setenv("TEST_ENVIRONMENT_FOO", "test_environment_foo"); err != nil {
+		t.Fatal(err)
+	}
+	thread := testSetup(t, []string{"CLI_FOO=foo"})
+	if _, err := starlark.ExecFile(thread, "cli_and_env.star", nil, builtins); err != nil {
+		if err, ok := err.(*starlark.EvalError); ok {
+			t.Fatal(err.Backtrace())
+		}
+		t.Fatal(err)
+	}
+}
+
+func TestFileOps(t *testing.T) {
+	// TODO(asmundak): convert this to use exerciseStarlarkTestFile
+	if err := os.Setenv("TEST_DATA_DIR", dataDir()); err != nil {
+		t.Fatal(err)
+	}
+	thread := testSetup(t, nil)
+	if _, err := starlark.ExecFile(thread, "file_ops.star", nil, builtins); err != nil {
+		if err, ok := err.(*starlark.EvalError); ok {
+			t.Fatal(err.Backtrace())
+		}
+		t.Fatal(err)
+	}
+}
+
+func TestLoad(t *testing.T) {
+	// TODO(asmundak): convert this to use exerciseStarlarkTestFile
+	thread := testSetup(t, nil)
+	thread.Load = func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
+		if module == "assert.star" {
+			return starlarktest.LoadAssertModule()
+		} else {
+			return loader(thread, module)
+		}
+	}
+	dir := dataDir()
+	thread.SetLocal(callerDirKey, dir)
+	LoadPathRoot = filepath.Dir(dir)
+	if _, err := starlark.ExecFile(thread, "load.star", nil, builtins); err != nil {
+		if err, ok := err.(*starlark.EvalError); ok {
+			t.Fatal(err.Backtrace())
+		}
+		t.Fatal(err)
+	}
+}
+
+func TestRegex(t *testing.T) {
+	exerciseStarlarkTestFile(t, "testdata/regex.star")
+}
+
+func TestShell(t *testing.T) {
+	if err := os.Setenv("TEST_DATA_DIR", dataDir()); err != nil {
+		t.Fatal(err)
+	}
+	exerciseStarlarkTestFile(t, "testdata/shell.star")
+}