bazel starlark: merge lib.syntax.util.EvaluationTestCase into BazelETC

//src/test/.../lib/syntax is now deleted.

The BETC diff is messy, even though the file was created by a move.
The only real changes are to the new{Module,Thread}Hook functions and
to the class declaration and its comments.

This change is a step towards a future in which tests do not (and cannot)
concern themselves with subtle internal details such as Starlark thread
creation.

PiperOrigin-RevId: 334417122
diff --git a/src/test/java/com/google/devtools/build/docgen/BUILD b/src/test/java/com/google/devtools/build/docgen/BUILD
index d261384..8703861 100644
--- a/src/test/java/com/google/devtools/build/docgen/BUILD
+++ b/src/test/java/com/google/devtools/build/docgen/BUILD
@@ -52,8 +52,6 @@
         "//src/main/java/net/starlark/java/annot",
         "//src/main/java/net/starlark/java/eval",
         "//src/main/java/net/starlark/java/syntax",
-        "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//third_party:guava",
         "//third_party:junit4",
         "//third_party:truth",
@@ -82,7 +80,6 @@
         "//src/main/java/net/starlark/java/eval",
         "//src/main/java/net/starlark/java/syntax",
         "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//third_party:guava",
         "//third_party:junit4",
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 2c071f1..0d6f149 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -44,7 +44,6 @@
         "//src/test/java/net/starlark/java/syntax:srcs",
         "//src/test/java/com/google/devtools/build/lib/starlarkdebug/server:srcs",
         "//src/test/java/com/google/devtools/build/lib/supplier:srcs",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util:srcs",
         "//src/test/java/com/google/devtools/build/lib/versioning:srcs",
         "//src/test/java/com/google/devtools/build/lib/vfs:srcs",
         "//src/test/java/com/google/devtools/build/lib/unsafe:srcs",
@@ -144,6 +143,7 @@
         ":AllTests",
         "//src/main/java/com/google/devtools/build/lib/collect",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+        "//src/test/java/com/google/devtools/build/lib/starlark/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:JunitUtils",
         "//third_party:guava",
@@ -167,7 +167,6 @@
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset:fingerprint_cache",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset:testutils",
-        "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization:constants",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils",
@@ -175,7 +174,7 @@
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/net/starlark/java/eval",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
+        "//src/test/java/com/google/devtools/build/lib/starlark/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:JunitUtils",
         "//third_party:guava",
diff --git a/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java b/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
index d2fd290..5ef6a39 100644
--- a/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
@@ -17,11 +17,8 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.collect.nestedset.Depset.ElementType;
-import com.google.devtools.build.lib.packages.StarlarkLibrary;
-import com.google.devtools.build.lib.packages.StructProvider;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
 import net.starlark.java.eval.Dict;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.StarlarkCallable;
@@ -37,15 +34,7 @@
 @RunWith(JUnit4.class)
 public final class DepsetTest {
 
-  private final EvaluationTestCase ev =
-      new EvaluationTestCase() {
-        @Override
-        protected Object newModuleHook(ImmutableMap.Builder<String, Object> predeclared) {
-          predeclared.put("depset", StarlarkLibrary.COMMON.get("depset"));
-          predeclared.put("struct", StructProvider.STRUCT);
-          return null; // no client data
-        }
-      };
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   @Test
   public void testConstructor() throws Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/rules/config/BUILD b/src/test/java/com/google/devtools/build/lib/rules/config/BUILD
index 70ef1c3..d19008e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/config/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/config/BUILD
@@ -41,7 +41,6 @@
         "//src/test/java/com/google/devtools/build/lib/actions/util",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
         "//third_party:guava",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTest.java b/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTest.java
index 3d80efb..914dcf2 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTest.java
@@ -27,7 +27,6 @@
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,7 +37,7 @@
 @RunWith(JUnit4.class)
 public final class ConfigFeatureFlagTest extends BuildViewTestCase {
 
-  private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   private StarlarkRuleContext createRuleContext(String label) throws Exception {
     return new StarlarkRuleContext(
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
index dd5796a..45fbea8 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -682,7 +682,7 @@
     deps = [
         "//src/main/java/net/starlark/java/eval",
         "//src/test/java/com/google/devtools/build/lib/packages:testutil",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
+        "//src/test/java/com/google/devtools/build/lib/starlark/util",
         "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
         "//third_party:junit4",
     ],
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcToolchainConfigureTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcToolchainConfigureTest.java
index 2b6d281..cdb1253 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcToolchainConfigureTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcToolchainConfigureTest.java
@@ -14,7 +14,7 @@
 package com.google.devtools.build.lib.rules.cpp;
 
 import com.google.devtools.build.lib.packages.util.ResourceLoader;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import java.io.IOException;
 import net.starlark.java.eval.Mutability;
@@ -25,7 +25,9 @@
 
 /** Tests for cc autoconfiguration. */
 @RunWith(JUnit4.class)
-public class StarlarkCcToolchainConfigureTest extends EvaluationTestCase {
+public class StarlarkCcToolchainConfigureTest {
+
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   @Test
   public void testSplitEscaped() throws Exception {
@@ -50,12 +52,8 @@
         .testExpression("split_escaped('a%:', ':')", StarlarkList.of(mu, "a:"));
   }
 
-  private Scenario newTest(String... starlarkOptions) throws IOException {
-    return new Scenario(starlarkOptions)
-        // A mock implementation of Label to be able to parse lib_cc_configure under default
-        // Starlark environment (lib_cc_configure is meant to be used from the repository
-        // environment).
-        .setUp("def Label(arg):\n  return 42")
+  private BazelEvaluationTestCase.Scenario newTest(String... starlarkOptions) throws IOException {
+    return ev.new Scenario(starlarkOptions)
         .setUp(
             ResourceLoader.readFromResources(
                 TestConstants.RULES_CC_REPOSITORY_EXECROOT
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
index 7090e31..609423b 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD
@@ -224,14 +224,12 @@
     name = "PyInfoTest",
     srcs = ["PyInfoTest.java"],
     deps = [
-        "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/rules/python",
         "//src/main/java/net/starlark/java/syntax",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//third_party:junit4",
         "//third_party:truth",
     ],
@@ -248,7 +246,6 @@
         "//src/main/java/net/starlark/java/syntax",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//third_party:junit4",
         "//third_party:truth",
     ],
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java
index b8ec1c1..c427c8e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java
@@ -22,7 +22,6 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import net.starlark.java.syntax.Location;
 import org.junit.Before;
 import org.junit.Test;
@@ -33,7 +32,7 @@
 @RunWith(JUnit4.class)
 public class PyInfoTest extends BuildViewTestCase {
 
-  private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   private Artifact dummyArtifact;
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyRuntimeInfoTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyRuntimeInfoTest.java
index db6dbbc..cb6d95f 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyRuntimeInfoTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PyRuntimeInfoTest.java
@@ -22,7 +22,6 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import net.starlark.java.syntax.Location;
 import org.junit.Before;
@@ -34,7 +33,7 @@
 @RunWith(JUnit4.class)
 public class PyRuntimeInfoTest extends BuildViewTestCase {
 
-  private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   private Artifact dummyInterpreter;
   private Artifact dummyFile;
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/BUILD b/src/test/java/com/google/devtools/build/lib/starlark/BUILD
index c9cccfe..c126752 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/starlark/BUILD
@@ -72,7 +72,6 @@
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/packages:testutil",
         "//src/test/java/com/google/devtools/build/lib/starlark/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:JunitUtils",
         "//third_party:guava",
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
index 8b104c7..7a87200 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -52,7 +52,6 @@
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.skyframe.BzlLoadFunction;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.MoreAsserts;
 import com.google.devtools.build.lib.util.FileTypeSet;
 import javax.annotation.Nullable;
@@ -78,7 +77,7 @@
 @RunWith(JUnit4.class)
 public final class StarlarkRuleClassFunctionsTest extends BuildViewTestCase {
 
-  private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   private StarlarkRuleContext createRuleContext(String label) throws Exception {
     return new StarlarkRuleContext(
@@ -735,7 +734,7 @@
     assertThat(c.hasAttr("a1", Type.STRING)).isTrue();
   }
 
-  private static void evalAndExport(EvaluationTestCase ev, String... lines) throws Exception {
+  private static void evalAndExport(BazelEvaluationTestCase ev, String... lines) throws Exception {
     ParserInput input = ParserInput.fromLines(lines);
     Module module = ev.getModule();
     StarlarkFile file = StarlarkFile.parse(input);
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
index 380feae..52465e3 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
@@ -52,7 +52,6 @@
 import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
 import com.google.devtools.build.lib.rules.python.PyProviderUtils;
 import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.build.lib.util.FileTypeSet;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -80,7 +79,7 @@
         getRuleContextForStarlark(getConfiguredTarget(label)), null, getStarlarkSemantics());
   }
 
-  private final EvaluationTestCase ev = new BazelEvaluationTestCase();
+  private final BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
 
   /** A test rule that exercises the semantics of mandatory providers. */
   private static final MockRule TESTING_RULE_FOR_MANDATORY_PROVIDERS =
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD b/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD
index 3f8eed9..5a36fc3 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/BUILD
@@ -30,9 +30,10 @@
         "//src/main/java/net/starlark/java/eval",
         "//src/main/java/net/starlark/java/syntax",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
-        "//src/test/java/com/google/devtools/build/lib/syntax/util",
+        "//src/test/java/com/google/devtools/build/lib/events:testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
         "//third_party:guava",
         "//third_party:junit4",
+        "//third_party:truth",
     ],
 )
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java
index a49d14b..3d638fd 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/BazelEvaluationTestCase.java
@@ -1,4 +1,4 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
+// Copyright 2006 The Bazel Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,44 +11,110 @@
 // 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 com.google.devtools.build.lib.starlark.util;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkModules;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventCollector;
+import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
 import com.google.devtools.build.lib.packages.BazelModuleContext;
 import com.google.devtools.build.lib.packages.BazelStarlarkContext;
 import com.google.devtools.build.lib.packages.SymbolGenerator;
+import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.rules.platform.PlatformCommon;
-import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.common.options.Options;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.util.ArrayList;
+import java.util.List;
+import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.Module;
+import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkThread;
+import net.starlark.java.syntax.Expression;
+import net.starlark.java.syntax.FileOptions;
+import net.starlark.java.syntax.ParserInput;
+import net.starlark.java.syntax.SyntaxError;
 
-/**
- * BazelEvaluationTestCase is a subclass of EvaluationTestCase that defines various Bazel built-ins
- * in the environment.
- */
-// TODO(adonovan): this is (and has always been) a mess, and an abuse of inheritance.
-// Once the production code API has disentangled Thread and Module, make this rational.
-public final class BazelEvaluationTestCase extends EvaluationTestCase {
+/** BazelEvaluationTestCase is a helper class for tests of Bazel loading-phase evaluation. */
+// TODO(adonovan): this helper class might be somewhat handy for testing core Starlark, but its
+// widespread use in tests of Bazel features greatly hinders the improvement of Bazel's loading
+// phase. The existence of tests based on this class forces Bazel to continue support scenarios in
+// which the test creates the environment, the threads, and so on, when these should be
+// implementation details of the loading phase. Instead, the lib.packages should present an API in
+// which the client provides files, flags, and arguments like a command-line tool, and all our tests
+// should be ported to use that API.
+public final class BazelEvaluationTestCase {
+  private final EventCollectionApparatus eventCollectionApparatus =
+      new EventCollectionApparatus(EventKind.ALL_EVENTS);
 
-  @Override
-  protected Object newModuleHook(ImmutableMap.Builder<String, Object> predeclared) {
-    StarlarkModules.addPredeclared(predeclared);
-    Starlark.addModule(predeclared, new PlatformCommon());
+  private StarlarkSemantics semantics = StarlarkSemantics.DEFAULT;
+  private StarlarkThread thread = null; // created lazily by getStarlarkThread
+  private Module module = null; // created lazily by getModule
 
-    // Return the module's client data. (This one uses dummy values for tests.)
-    return BazelModuleContext.create(
-        Label.parseAbsoluteUnchecked("//test:label", /*defaultToMain=*/ false),
-        "test/label.bzl",
-        /*loads=*/ ImmutableMap.of(),
-        /*bzlTransitiveDigest=*/ new byte[0]);
+  /**
+   * Parses the semantics flags and updates the semantics used to filter predeclared bindings, and
+   * carried by subsequently created threads. Causes a new StarlarkThread and Module to be created
+   * when next needed.
+   */
+  public final void setSemantics(String... options) throws OptionsParsingException {
+    this.semantics =
+        Options.parse(BuildLanguageOptions.class, options).getOptions().toStarlarkSemantics();
+
+    // Re-initialize the thread and module with the new semantics when needed.
+    this.thread = null;
+    this.module = null;
   }
 
-  @Override
-  protected void newThreadHook(StarlarkThread thread) {
+  public ExtendedEventHandler getEventHandler() {
+    return eventCollectionApparatus.reporter();
+  }
+
+  // TODO(adonovan): don't let subclasses inherit vaguely specified "helpers".
+  // Separate all the tests clearly into tests of the scanner, parser, resolver,
+  // and evaluation.
+
+  /** Parses an expression. */
+  final Expression parseExpression(String... lines) throws SyntaxError.Exception {
+    return Expression.parse(ParserInput.fromLines(lines));
+  }
+
+  /** Updates a global binding in the module. */
+  // TODO(adonovan): rename setGlobal.
+  public BazelEvaluationTestCase update(String varname, Object value) throws Exception {
+    getModule().setGlobal(varname, value);
+    return this;
+  }
+
+  /** Returns the value of a global binding in the module. */
+  // TODO(adonovan): rename getGlobal.
+  public Object lookup(String varname) throws Exception {
+    return getModule().getGlobal(varname);
+  }
+
+  /** Joins the lines, parses them as an expression, and evaluates it. */
+  public final Object eval(String... lines) throws Exception {
+    ParserInput input = ParserInput.fromLines(lines);
+    return Starlark.eval(input, FileOptions.DEFAULT, getModule(), getStarlarkThread());
+  }
+
+  /** Joins the lines, parses them as a file, and executes it. */
+  public final void exec(String... lines)
+      throws SyntaxError.Exception, EvalException, InterruptedException {
+    ParserInput input = ParserInput.fromLines(lines);
+    Starlark.execFile(input, FileOptions.DEFAULT, getModule(), getStarlarkThread());
+  }
+
+  private static void newThread(StarlarkThread thread) {
     // This StarlarkThread has no PackageContext, so attempts to create a rule will fail.
     // Rule creation is tested by StarlarkIntegrationTest.
 
@@ -65,4 +131,325 @@
             /*analysisRuleLabel=*/ null) // dummy value for tests
         .storeInThread(thread);
   }
+
+  private static Object newModule(ImmutableMap.Builder<String, Object> predeclared) {
+    StarlarkModules.addPredeclared(predeclared);
+    Starlark.addModule(predeclared, new PlatformCommon());
+
+    // Return the module's client data. (This one uses dummy values for tests.)
+    return BazelModuleContext.create(
+        Label.parseAbsoluteUnchecked("//test:label", /*defaultToMain=*/ false),
+        "test/label.bzl",
+        /*loads=*/ ImmutableMap.of(),
+        /*bzlTransitiveDigest=*/ new byte[0]);
+  }
+
+  public StarlarkThread getStarlarkThread() {
+    if (this.thread == null) {
+      Mutability mu = Mutability.create("test");
+      StarlarkThread thread = new StarlarkThread(mu, semantics);
+      thread.setPrintHandler(Event.makeDebugPrintHandler(getEventHandler()));
+      newThread(thread);
+      this.thread = thread;
+    }
+    return this.thread;
+  }
+
+  public Module getModule() {
+    if (this.module == null) {
+      ImmutableMap.Builder<String, Object> predeclared = ImmutableMap.builder();
+      Object clientData = newModule(predeclared);
+      Module module = Module.withPredeclared(semantics, predeclared.build());
+      module.setClientData(clientData);
+      this.module = module;
+    }
+    return this.module;
+  }
+
+  public void checkEvalError(String msg, String... input) throws Exception {
+    try {
+      exec(input);
+      fail("Expected error '" + msg + "' but got no error");
+    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
+      assertThat(e).hasMessageThat().isEqualTo(msg);
+    }
+  }
+
+  public void checkEvalErrorContains(String msg, String... input) throws Exception {
+    try {
+      exec(input);
+      fail("Expected error containing '" + msg + "' but got no error");
+    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
+      assertThat(e).hasMessageThat().contains(msg);
+    }
+  }
+
+  public void checkEvalErrorDoesNotContain(String msg, String... input) throws Exception {
+    try {
+      exec(input);
+    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
+      assertThat(e).hasMessageThat().doesNotContain(msg);
+    }
+  }
+
+  // Forward relevant methods to the EventCollectionApparatus
+  public BazelEvaluationTestCase setFailFast(boolean failFast) {
+    eventCollectionApparatus.setFailFast(failFast);
+    return this;
+  }
+
+  public BazelEvaluationTestCase assertNoWarningsOrErrors() {
+    eventCollectionApparatus.assertNoWarningsOrErrors();
+    return this;
+  }
+
+  public EventCollector getEventCollector() {
+    return eventCollectionApparatus.collector();
+  }
+
+  public Event assertContainsError(String expectedMessage) {
+    return eventCollectionApparatus.assertContainsError(expectedMessage);
+  }
+
+  public Event assertContainsWarning(String expectedMessage) {
+    return eventCollectionApparatus.assertContainsWarning(expectedMessage);
+  }
+
+  public Event assertContainsDebug(String expectedMessage) {
+    return eventCollectionApparatus.assertContainsDebug(expectedMessage);
+  }
+
+  public BazelEvaluationTestCase clearEvents() {
+    eventCollectionApparatus.clear();
+    return this;
+  }
+
+  /** Encapsulates a separate test which can be executed by a Scenario. */
+  protected interface Testable {
+    void run() throws Exception;
+  }
+
+  /**
+   * A test scenario (a script of steps). Beware: Scenario is an inner class that mutates its
+   * enclosing BazelEvaluationTestCase as it executes the script.
+   */
+  public final class Scenario {
+    private final SetupActions setup = new SetupActions();
+    private final String[] starlarkOptions;
+
+    public Scenario(String... starlarkOptions) {
+      this.starlarkOptions = starlarkOptions;
+    }
+
+    private void run(Testable testable) throws Exception {
+      setSemantics(starlarkOptions);
+      testable.run();
+    }
+
+    /** Allows the execution of several statements before each following test. */
+    public Scenario setUp(String... lines) {
+      setup.registerExec(lines);
+      return this;
+    }
+
+    /**
+     * Allows the update of the specified variable before each following test
+     *
+     * @param name The name of the variable that should be updated
+     * @param value The new value of the variable
+     * @return This {@code Scenario}
+     */
+    public Scenario update(String name, Object value) {
+      setup.registerUpdate(name, value);
+      return this;
+    }
+
+    /**
+     * Evaluates two expressions and asserts that their results are equal.
+     *
+     * @param src The source expression to be evaluated
+     * @param expectedEvalString The expression of the expected result
+     * @return This {@code Scenario}
+     * @throws Exception
+     */
+    public Scenario testEval(String src, String expectedEvalString) throws Exception {
+      runTest(createComparisonTestable(src, expectedEvalString, true));
+      return this;
+    }
+
+    /** Evaluates an expression and compares its result to the expected object. */
+    public Scenario testExpression(String src, Object expected) throws Exception {
+      runTest(createComparisonTestable(src, expected, false));
+      return this;
+    }
+
+    /** Evaluates an expression and compares its result to the ordered list of expected objects. */
+    public Scenario testExactOrder(String src, Object... items) throws Exception {
+      runTest(collectionTestable(src, items));
+      return this;
+    }
+
+    /** Evaluates an expression and checks whether it fails with the expected error. */
+    public Scenario testIfExactError(String expectedError, String... lines) throws Exception {
+      runTest(errorTestable(true, expectedError, lines));
+      return this;
+    }
+
+    /** Evaluates the expression and checks whether it fails with the expected error. */
+    public Scenario testIfErrorContains(String expectedError, String... lines) throws Exception {
+      runTest(errorTestable(false, expectedError, lines));
+      return this;
+    }
+
+    /** Looks up the value of the specified variable and compares it to the expected value. */
+    public Scenario testLookup(String name, Object expected) throws Exception {
+      runTest(createLookUpTestable(name, expected));
+      return this;
+    }
+
+    /**
+     * Creates a Testable that checks whether the evaluation of the given expression fails with the
+     * expected error.
+     *
+     * @param exactMatch whether the error message must be identical to the expected error.
+     */
+    private Testable errorTestable(
+        final boolean exactMatch, final String error, final String... lines) {
+      return new Testable() {
+        @Override
+        public void run() throws Exception {
+          if (exactMatch) {
+            checkEvalError(error, lines);
+          } else {
+            checkEvalErrorContains(error, lines);
+          }
+        }
+      };
+    }
+
+    /**
+     * Creates a Testable that checks whether the value of the expression is a sequence containing
+     * the expected elements.
+     */
+    private Testable collectionTestable(final String src, final Object... expected) {
+      return new Testable() {
+        @Override
+        public void run() throws Exception {
+          assertThat((Iterable<?>) eval(src)).containsExactly(expected).inOrder();
+        }
+      };
+    }
+
+    /**
+     * Creates a testable that compares the value of the expression to a specified result.
+     *
+     * @param src The expression to be evaluated
+     * @param expected Either the expected object or an expression whose evaluation leads to the
+     *     expected object
+     * @param expectedIsExpression Signals whether {@code expected} is an object or an expression
+     * @return An instance of Testable that runs the comparison
+     */
+    private Testable createComparisonTestable(
+        final String src, final Object expected, final boolean expectedIsExpression) {
+      return new Testable() {
+        @Override
+        public void run() throws Exception {
+          Object actual = eval(src);
+          Object realExpected = expected;
+
+          // We could also print the actual object and compare the string to the expected
+          // expression, but then the order of elements would matter.
+          if (expectedIsExpression) {
+            realExpected = eval((String) expected);
+          }
+
+          assertThat(actual).isEqualTo(realExpected);
+        }
+      };
+    }
+
+    /**
+     * Creates a Testable that looks up the given variable and compares its value to the expected
+     * value
+     *
+     * @param name
+     * @param expected
+     * @return An instance of Testable that does both lookup and comparison
+     */
+    private Testable createLookUpTestable(final String name, final Object expected) {
+      return new Testable() {
+        @Override
+        public void run() throws Exception {
+          assertThat(lookup(name)).isEqualTo(expected);
+        }
+      };
+    }
+
+    /** Executes the given Testable */
+    void runTest(Testable testable) throws Exception {
+      run(new TestableDecorator(setup, testable));
+    }
+  }
+
+  /**
+   * A simple decorator that allows the execution of setup actions before running a {@code Testable}
+   */
+  static class TestableDecorator implements Testable {
+    private final SetupActions setup;
+    private final Testable decorated;
+
+    public TestableDecorator(SetupActions setup, Testable decorated) {
+      this.setup = setup;
+      this.decorated = decorated;
+    }
+
+    /** Executes all stored actions and updates plus the actual {@code Testable} */
+    @Override
+    public void run() throws Exception {
+      setup.executeAll();
+      decorated.run();
+    }
+  }
+
+  /** A container for collection actions that should be executed before a test */
+  class SetupActions {
+    private final List<Testable> setup;
+
+    public SetupActions() {
+      setup = new ArrayList<>();
+    }
+
+    /**
+     * Registers an update to a module variable to be bound before a test
+     *
+     * @param name
+     */
+    public void registerUpdate(final String name, final Object value) {
+      setup.add(
+          new Testable() {
+            @Override
+            public void run() throws Exception {
+              BazelEvaluationTestCase.this.update(name, value);
+            }
+          });
+    }
+
+    /** Registers a sequence of statements for execution prior to a test. */
+    public void registerExec(final String... lines) {
+      setup.add(
+          new Testable() {
+            @Override
+            public void run() throws Exception {
+              BazelEvaluationTestCase.this.exec(lines);
+            }
+          });
+    }
+
+    /** Executes all stored actions and updates */
+    public void executeAll() throws Exception {
+      for (Testable testable : setup) {
+        testable.run();
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/util/BUILD b/src/test/java/com/google/devtools/build/lib/syntax/util/BUILD
deleted file mode 100644
index f6828ba..0000000
--- a/src/test/java/com/google/devtools/build/lib/syntax/util/BUILD
+++ /dev/null
@@ -1,26 +0,0 @@
-package(
-    default_visibility = ["//src:__subpackages__"],
-)
-
-filegroup(
-    name = "srcs",
-    srcs = glob(["**"]),
-    visibility = ["//src:__subpackages__"],
-)
-
-java_library(
-    name = "util",
-    testonly = 1,
-    srcs = glob(["*.java"]),
-    deps = [
-        "//src/main/java/com/google/devtools/build/lib/events",
-        "//src/main/java/com/google/devtools/build/lib/packages/semantics",
-        "//src/main/java/com/google/devtools/common/options",
-        "//src/main/java/net/starlark/java/eval",
-        "//src/main/java/net/starlark/java/syntax",
-        "//src/test/java/com/google/devtools/build/lib/events:testutil",
-        "//third_party:guava",
-        "//third_party:junit4",
-        "//third_party:truth",
-    ],
-)
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
deleted file mode 100644
index dc4e466..0000000
--- a/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2006 The Bazel Authors. All rights reserved.
-//
-// 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 com.google.devtools.build.lib.syntax.util;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.EventCollector;
-import com.google.devtools.build.lib.events.EventKind;
-import com.google.devtools.build.lib.events.ExtendedEventHandler;
-import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
-import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
-import com.google.devtools.common.options.Options;
-import com.google.devtools.common.options.OptionsParsingException;
-import java.util.LinkedList;
-import java.util.List;
-import net.starlark.java.eval.EvalException;
-import net.starlark.java.eval.Module;
-import net.starlark.java.eval.Mutability;
-import net.starlark.java.eval.Starlark;
-import net.starlark.java.eval.StarlarkSemantics;
-import net.starlark.java.eval.StarlarkThread;
-import net.starlark.java.syntax.Expression;
-import net.starlark.java.syntax.FileOptions;
-import net.starlark.java.syntax.ParserInput;
-import net.starlark.java.syntax.SyntaxError;
-
-/** Helper class for tests that evaluate Starlark code. */
-// TODO(adonovan): stop extending this class. Prefer composition over inheritance.
-// Rename it to EvaluationApparatus for consistency.
-//
-// TODO(adonovan): make predeclared env + semantics more like normal parameters.
-// The main challenge is when are the Thread and Module created?
-// They should have a consistent semantics, and the predeclared environment
-// cannot be changed after the Module is created.
-// Also, the fact that exec/eval/update/lookup can be used directly
-// or through a Scenario complicates the question of when we commit to
-// predeclared env + semantics.
-// For the most part, the predeclared env doesn't vary across a suite,
-// so it could be a constructor parameter.
-//
-// TODO(adonovan): this helper class might be somewhat handy for testing core Starlark, but its
-// widespread use in tests of Bazel features greatly hinders the improvement of Bazel's loading
-// phase. The existence of tests based on this class forces Bazel to continue support scenarios in
-// which the test creates the environment, the threads, and so on, when these should be
-// implemenation details of the loading phase. Instead, the lib.packages should present an API in
-// which the client provides files, flags, and arguments like a command-line tool, and all our tests
-// should be ported to use that API.
-public class EvaluationTestCase {
-  private EventCollectionApparatus eventCollectionApparatus =
-      new EventCollectionApparatus(EventKind.ALL_EVENTS);
-
-  private StarlarkSemantics semantics = StarlarkSemantics.DEFAULT;
-  private StarlarkThread thread = null; // created lazily by getStarlarkThread
-  private Module module = null; // created lazily by getModule
-
-  /**
-   * Parses the semantics flags and updates the semantics used to filter predeclared bindings, and
-   * carried by subsequently created threads. Causes a new StarlarkThread and Module to be created
-   * when next needed.
-   */
-  public final void setSemantics(String... options) throws OptionsParsingException {
-    this.semantics =
-        Options.parse(BuildLanguageOptions.class, options).getOptions().toStarlarkSemantics();
-
-    // Re-initialize the thread and module with the new semantics when needed.
-    this.thread = null;
-    this.module = null;
-  }
-
-  public ExtendedEventHandler getEventHandler() {
-    return eventCollectionApparatus.reporter();
-  }
-
-  // TODO(adonovan): don't let subclasses inherit vaguely specified "helpers".
-  // Separate all the tests clearly into tests of the scanner, parser, resolver,
-  // and evaluation.
-
-  /** Parses an expression. */
-  protected final Expression parseExpression(String... lines) throws SyntaxError.Exception {
-    return Expression.parse(ParserInput.fromLines(lines));
-  }
-
-  /** Updates a global binding in the module. */
-  // TODO(adonovan): rename setGlobal.
-  public EvaluationTestCase update(String varname, Object value) throws Exception {
-    getModule().setGlobal(varname, value);
-    return this;
-  }
-
-  /** Returns the value of a global binding in the module. */
-  // TODO(adonovan): rename getGlobal.
-  public Object lookup(String varname) throws Exception {
-    return getModule().getGlobal(varname);
-  }
-
-  /** Joins the lines, parses them as an expression, and evaluates it. */
-  public final Object eval(String... lines) throws Exception {
-    ParserInput input = ParserInput.fromLines(lines);
-    return Starlark.eval(input, FileOptions.DEFAULT, getModule(), getStarlarkThread());
-  }
-
-  /** Joins the lines, parses them as a file, and executes it. */
-  public final void exec(String... lines)
-      throws SyntaxError.Exception, EvalException, InterruptedException {
-    ParserInput input = ParserInput.fromLines(lines);
-    Starlark.execFile(input, FileOptions.DEFAULT, getModule(), getStarlarkThread());
-  }
-
-  // A hook for subclasses to alter a newly created thread,
-  // e.g. by inserting thread-local values.
-  protected void newThreadHook(StarlarkThread thread) {}
-
-  // A hook for subclasses to alter the created module.
-  // Implementations may add to the predeclared environment,
-  // and return the module's client data value.
-  protected Object newModuleHook(ImmutableMap.Builder<String, Object> predeclared) {
-    return null; // no client data
-  }
-
-  public StarlarkThread getStarlarkThread() {
-    if (this.thread == null) {
-      Mutability mu = Mutability.create("test");
-      StarlarkThread thread = new StarlarkThread(mu, semantics);
-      thread.setPrintHandler(Event.makeDebugPrintHandler(getEventHandler()));
-      newThreadHook(thread);
-      this.thread = thread;
-    }
-    return this.thread;
-  }
-
-  public Module getModule() {
-    if (this.module == null) {
-      ImmutableMap.Builder<String, Object> predeclared = ImmutableMap.builder();
-      Object clientData = newModuleHook(predeclared);
-      Module module = Module.withPredeclared(semantics, predeclared.build());
-      module.setClientData(clientData);
-      this.module = module;
-    }
-    return this.module;
-  }
-
-  public void checkEvalError(String msg, String... input) throws Exception {
-    try {
-      exec(input);
-      fail("Expected error '" + msg + "' but got no error");
-    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
-      assertThat(e).hasMessageThat().isEqualTo(msg);
-    }
-  }
-
-  public void checkEvalErrorContains(String msg, String... input) throws Exception {
-    try {
-      exec(input);
-      fail("Expected error containing '" + msg + "' but got no error");
-    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
-      assertThat(e).hasMessageThat().contains(msg);
-    }
-  }
-
-  public void checkEvalErrorDoesNotContain(String msg, String... input) throws Exception {
-    try {
-      exec(input);
-    } catch (SyntaxError.Exception | EvalException | EventCollectionApparatus.FailFastException e) {
-      assertThat(e).hasMessageThat().doesNotContain(msg);
-    }
-  }
-
-  // Forward relevant methods to the EventCollectionApparatus
-  public EvaluationTestCase setFailFast(boolean failFast) {
-    eventCollectionApparatus.setFailFast(failFast);
-    return this;
-  }
-
-  public EvaluationTestCase assertNoWarningsOrErrors() {
-    eventCollectionApparatus.assertNoWarningsOrErrors();
-    return this;
-  }
-
-  public EventCollector getEventCollector() {
-    return eventCollectionApparatus.collector();
-  }
-
-  public Event assertContainsError(String expectedMessage) {
-    return eventCollectionApparatus.assertContainsError(expectedMessage);
-  }
-
-  public Event assertContainsWarning(String expectedMessage) {
-    return eventCollectionApparatus.assertContainsWarning(expectedMessage);
-  }
-
-  public Event assertContainsDebug(String expectedMessage) {
-    return eventCollectionApparatus.assertContainsDebug(expectedMessage);
-  }
-
-  public EvaluationTestCase clearEvents() {
-    eventCollectionApparatus.clear();
-    return this;
-  }
-
-  /** Encapsulates a separate test which can be executed by a Scenario. */
-  protected interface Testable {
-    void run() throws Exception;
-  }
-
-  /**
-   * A test scenario (a script of steps). Beware: Scenario is an inner class that mutates its
-   * enclosing EvaluationTestCase as it executes the script.
-   */
-  public final class Scenario {
-    private final SetupActions setup = new SetupActions();
-    private final String[] starlarkOptions;
-
-    public Scenario(String... starlarkOptions) {
-      this.starlarkOptions = starlarkOptions;
-    }
-
-    private void run(Testable testable) throws Exception {
-      setSemantics(starlarkOptions);
-      testable.run();
-    }
-
-    /** Allows the execution of several statements before each following test. */
-    public Scenario setUp(String... lines) {
-      setup.registerExec(lines);
-      return this;
-    }
-
-    /**
-     * Allows the update of the specified variable before each following test
-     *
-     * @param name The name of the variable that should be updated
-     * @param value The new value of the variable
-     * @return This {@code Scenario}
-     */
-    public Scenario update(String name, Object value) {
-      setup.registerUpdate(name, value);
-      return this;
-    }
-
-    /**
-     * Evaluates two expressions and asserts that their results are equal.
-     *
-     * @param src The source expression to be evaluated
-     * @param expectedEvalString The expression of the expected result
-     * @return This {@code Scenario}
-     * @throws Exception
-     */
-    public Scenario testEval(String src, String expectedEvalString) throws Exception {
-      runTest(createComparisonTestable(src, expectedEvalString, true));
-      return this;
-    }
-
-    /** Evaluates an expression and compares its result to the expected object. */
-    public Scenario testExpression(String src, Object expected) throws Exception {
-      runTest(createComparisonTestable(src, expected, false));
-      return this;
-    }
-
-    /** Evaluates an expression and compares its result to the ordered list of expected objects. */
-    public Scenario testExactOrder(String src, Object... items) throws Exception {
-      runTest(collectionTestable(src, items));
-      return this;
-    }
-
-    /** Evaluates an expression and checks whether it fails with the expected error. */
-    public Scenario testIfExactError(String expectedError, String... lines) throws Exception {
-      runTest(errorTestable(true, expectedError, lines));
-      return this;
-    }
-
-    /** Evaluates the expresson and checks whether it fails with the expected error. */
-    public Scenario testIfErrorContains(String expectedError, String... lines) throws Exception {
-      runTest(errorTestable(false, expectedError, lines));
-      return this;
-    }
-
-    /** Looks up the value of the specified variable and compares it to the expected value. */
-    public Scenario testLookup(String name, Object expected) throws Exception {
-      runTest(createLookUpTestable(name, expected));
-      return this;
-    }
-
-    /**
-     * Creates a Testable that checks whether the evaluation of the given expression fails with the
-     * expected error.
-     *
-     * @param exactMatch whether the error message must be identical to the expected error.
-     */
-    private Testable errorTestable(
-        final boolean exactMatch, final String error, final String... lines) {
-      return new Testable() {
-        @Override
-        public void run() throws Exception {
-          if (exactMatch) {
-            checkEvalError(error, lines);
-          } else {
-            checkEvalErrorContains(error, lines);
-          }
-        }
-      };
-    }
-
-    /**
-     * Creates a Testable that checks whether the value of the expression is a sequence containing
-     * the expected elements.
-     */
-    private Testable collectionTestable(final String src, final Object... expected) {
-      return new Testable() {
-        @Override
-        public void run() throws Exception {
-          assertThat((Iterable<?>) eval(src)).containsExactly(expected).inOrder();
-        }
-      };
-    }
-
-    /**
-     * Creates a testable that compares the value of the expression to a specified result.
-     *
-     * @param src The expression to be evaluated
-     * @param expected Either the expected object or an expression whose evaluation leads to the
-     *     expected object
-     * @param expectedIsExpression Signals whether {@code expected} is an object or an expression
-     * @return An instance of Testable that runs the comparison
-     */
-    private Testable createComparisonTestable(
-        final String src, final Object expected, final boolean expectedIsExpression) {
-      return new Testable() {
-        @Override
-        public void run() throws Exception {
-          Object actual = eval(src);
-          Object realExpected = expected;
-
-          // We could also print the actual object and compare the string to the expected
-          // expression, but then the order of elements would matter.
-          if (expectedIsExpression) {
-            realExpected = eval((String) expected);
-          }
-
-          assertThat(actual).isEqualTo(realExpected);
-        }
-      };
-    }
-
-    /**
-     * Creates a Testable that looks up the given variable and compares its value to the expected
-     * value
-     *
-     * @param name
-     * @param expected
-     * @return An instance of Testable that does both lookup and comparison
-     */
-    private Testable createLookUpTestable(final String name, final Object expected) {
-      return new Testable() {
-        @Override
-        public void run() throws Exception {
-          assertThat(lookup(name)).isEqualTo(expected);
-        }
-      };
-    }
-
-    /**
-     * Executes the given Testable
-     * @param testable
-     * @throws Exception
-     */
-    protected void runTest(Testable testable) throws Exception {
-      run(new TestableDecorator(setup, testable));
-    }
-  }
-
-  /**
-   * A simple decorator that allows the execution of setup actions before running a {@code Testable}
-   */
-  static class TestableDecorator implements Testable {
-    private final SetupActions setup;
-    private final Testable decorated;
-
-    public TestableDecorator(SetupActions setup, Testable decorated) {
-      this.setup = setup;
-      this.decorated = decorated;
-    }
-
-    /**
-     * Executes all stored actions and updates plus the actual {@code Testable}
-     */
-    @Override
-    public void run() throws Exception {
-      setup.executeAll();
-      decorated.run();
-    }
-  }
-
-  /**
-   * A container for collection actions that should be executed before a test
-   */
-  class SetupActions {
-    private List<Testable> setup;
-
-    public SetupActions() {
-      setup = new LinkedList<>();
-    }
-
-    /**
-     * Registers an update to a module variable to be bound before a test
-     *
-     * @param name
-     * @param value
-     */
-    public void registerUpdate(final String name, final Object value) {
-      setup.add(
-          new Testable() {
-            @Override
-            public void run() throws Exception {
-              EvaluationTestCase.this.update(name, value);
-            }
-          });
-    }
-
-    /** Registers a sequence of statements for execution prior to a test. */
-    public void registerExec(final String... lines) {
-      setup.add(
-          new Testable() {
-            @Override
-            public void run() throws Exception {
-              EvaluationTestCase.this.exec(lines);
-            }
-          });
-    }
-
-    /**
-     * Executes all stored actions and updates
-     * @throws Exception
-     */
-    public void executeAll() throws Exception {
-      for (Testable testable : setup) {
-        testable.run();
-      }
-    }
-  }
-}