rust: refactor tests setup

Move to a builder pattern to increase flexibility when generating the
test configuration. The testRust, testRustCov and testRustError are kept
as main entry points for standard tests. Add documentation.

Test: m nothing
Change-Id: I891bec982ff2d65413f150d2395edf0fb0d68a43
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 26e943a..ec78860 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -54,10 +54,58 @@
 	os.Exit(run())
 }
 
-func testConfig(bp string) android.Config {
-	bp = bp + GatherRequiredDepsForTest()
+// testRust returns a TestContext in which a basic environment has been setup.
+// This environment contains a few mocked files. See testRustCtx.useMockedFs
+// for the list of these files.
+func testRust(t *testing.T, bp string) *android.TestContext {
+	tctx := newTestRustCtx(t, bp)
+	tctx.useMockedFs()
+	tctx.generateConfig()
+	return tctx.parse(t)
+}
 
-	fs := map[string][]byte{
+// testRustCov returns a TestContext in which a basic environment has been
+// setup. This environment explicitly enables coverage.
+func testRustCov(t *testing.T, bp string) *android.TestContext {
+	tctx := newTestRustCtx(t, bp)
+	tctx.useMockedFs()
+	tctx.generateConfig()
+	tctx.enableCoverage(t)
+	return tctx.parse(t)
+}
+
+// testRustError ensures that at least one error was raised and its value
+// matches the pattern provided. The error can be either in the parsing of the
+// Blueprint or when generating the build actions.
+func testRustError(t *testing.T, pattern string, bp string) {
+	tctx := newTestRustCtx(t, bp)
+	tctx.useMockedFs()
+	tctx.generateConfig()
+	tctx.parseError(t, pattern)
+}
+
+// testRustCtx is used to build a particular test environment. Unless your
+// tests requires a specific setup, prefer the wrapping functions: testRust,
+// testRustCov or testRustError.
+type testRustCtx struct {
+	bp     string
+	fs     map[string][]byte
+	env    map[string]string
+	config *android.Config
+}
+
+// newTestRustCtx returns a new testRustCtx for the Blueprint definition argument.
+func newTestRustCtx(t *testing.T, bp string) *testRustCtx {
+	// TODO (b/140435149)
+	if runtime.GOOS != "linux" {
+		t.Skip("Rust Soong tests can only be run on Linux hosts currently")
+	}
+	return &testRustCtx{bp: bp}
+}
+
+// useMockedFs setup a default mocked filesystem for the test environment.
+func (tctx *testRustCtx) useMockedFs() {
+	tctx.fs = map[string][]byte{
 		"foo.rs":     nil,
 		"foo.c":      nil,
 		"src/bar.rs": nil,
@@ -66,57 +114,51 @@
 		"liby.so":    nil,
 		"libz.so":    nil,
 	}
-
-	cc.GatherRequiredFilesForTest(fs)
-
-	return android.TestArchConfig(buildDir, nil, bp, fs)
 }
 
-func testRust(t *testing.T, bp string) *android.TestContext {
-	return testRustContext(t, bp, false)
+// generateConfig creates the android.Config based on the bp, fs and env
+// attributes of the testRustCtx.
+func (tctx *testRustCtx) generateConfig() {
+	tctx.bp = tctx.bp + GatherRequiredDepsForTest()
+	cc.GatherRequiredFilesForTest(tctx.fs)
+	config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
+	tctx.config = &config
 }
 
-func testRustCov(t *testing.T, bp string) *android.TestContext {
-	return testRustContext(t, bp, true)
-}
-
-func testRustContext(t *testing.T, bp string, coverage bool) *android.TestContext {
-	// TODO (b/140435149)
-	if runtime.GOOS != "linux" {
-		t.Skip("Only the Linux toolchain is supported for Rust")
+// enableCoverage configures the test to enable coverage.
+func (tctx *testRustCtx) enableCoverage(t *testing.T) {
+	if tctx.config == nil {
+		t.Fatalf("tctx.config not been generated yet. Please call generateConfig first.")
 	}
+	tctx.config.TestProductVariables.GcovCoverage = proptools.BoolPtr(true)
+	tctx.config.TestProductVariables.Native_coverage = proptools.BoolPtr(true)
+	tctx.config.TestProductVariables.NativeCoveragePaths = []string{"*"}
+}
 
-	t.Helper()
-	config := testConfig(bp)
-
-	if coverage {
-		config.TestProductVariables.GcovCoverage = proptools.BoolPtr(true)
-		config.TestProductVariables.Native_coverage = proptools.BoolPtr(true)
-		config.TestProductVariables.NativeCoveragePaths = []string{"*"}
+// parse validates the configuration and parses the Blueprint file. It returns
+// a TestContext which can be used to retrieve the generated modules via
+// ModuleForTests.
+func (tctx testRustCtx) parse(t *testing.T) *android.TestContext {
+	if tctx.config == nil {
+		t.Fatalf("tctx.config not been generated yet. Please call generateConfig first.")
 	}
-
 	ctx := CreateTestContext()
-	ctx.Register(config)
-
+	ctx.Register(*tctx.config)
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
+	_, errs = ctx.PrepareBuildActions(*tctx.config)
 	android.FailIfErrored(t, errs)
-
 	return ctx
 }
 
-func testRustError(t *testing.T, pattern string, bp string) {
-	// TODO (b/140435149)
-	if runtime.GOOS != "linux" {
-		t.Skip("Only the Linux toolchain is supported for Rust")
+// parseError parses the Blueprint file and ensure that at least one error
+// matching the provided pattern is observed.
+func (tctx testRustCtx) parseError(t *testing.T, pattern string) {
+	if tctx.config == nil {
+		t.Fatalf("tctx.config not been generated yet. Please call generateConfig first.")
 	}
-
-	t.Helper()
-	config := testConfig(bp)
-
 	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx.Register(*tctx.config)
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	if len(errs) > 0 {
@@ -124,7 +166,7 @@
 		return
 	}
 
-	_, errs = ctx.PrepareBuildActions(config)
+	_, errs = ctx.PrepareBuildActions(*tctx.config)
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
 		return