Open source some skylark tests.

--
MOS_MIGRATED_REVID=103652672
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
index f3ea46c..69f8e01 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java
@@ -320,7 +320,7 @@
   };
 
   // This class is needed for testing
-  static final class RuleFunction extends BaseFunction {
+  public static final class RuleFunction extends BaseFunction {
     // Note that this means that we can reuse the same builder.
     // This is fine since we don't modify the builder from here.
     private final RuleClass.Builder builder;
@@ -367,7 +367,7 @@
     }
 
     @VisibleForTesting
-    RuleClass.Builder getBuilder() {
+    public RuleClass.Builder getBuilder() {
       return builder;
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index c283157..37769bb 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -393,7 +393,8 @@
   // TODO(bazel-team): Delete this function.
   // This function is currently used in various functions that change their behavior with respect to
   // lists depending on the Skylark-ness of the code; lists should be unified between the two modes.
-  boolean isSkylark() {
+  @VisibleForTesting
+  public boolean isSkylark() {
     return isSkylark;
   }
 
@@ -900,7 +901,7 @@
    * @param input a list of lines of code
    */
   @VisibleForTesting
-  List<Statement> parseFile(String... input) {
+  public List<Statement> parseFile(String... input) {
     return parseFileWithComments(input).statements;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index fb95228..a385e25 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -45,9 +45,9 @@
  * Recursive descent parser for LL(2) BUILD language.
  * Loosely based on Python 2 grammar.
  * See https://docs.python.org/2/reference/grammar.html
- *
  */
-class Parser {
+@VisibleForTesting
+public class Parser {
 
   /**
    * Combines the parser result into a single value object.
diff --git a/src/test/java/BUILD b/src/test/java/BUILD
index 64148e6..f9d7a59 100644
--- a/src/test/java/BUILD
+++ b/src/test/java/BUILD
@@ -536,6 +536,89 @@
     args = ["com.google.devtools.build.lib.AllTests"],
     deps = [
         ":foundations_testutil",
+        ":syntax_testutil",
+        ":test_runner",
+        ":testutil",
+        "//src/main/java:actions",
+        "//src/main/java:analysis-exec-rules-skyframe",
+        "//src/main/java:bazel-core",
+        "//src/main/java:collect",
+        "//src/main/java:concurrent",
+        "//src/main/java:events",
+        "//src/main/java:packages",
+        "//src/main/java:util",
+        "//src/main/java:vfs",
+        "//third_party:guava",
+        "//third_party:guava-testlib",
+        "//third_party:jsr305",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
+
+java_library(
+    name = "syntax_testutil",
+    srcs = glob([
+        "com/google/devtools/build/lib/syntax/util/*.java",
+    ]),
+    deps = [
+        ":foundations_testutil",
+        ":test_runner",
+        ":testutil",
+        "//src/main/java:actions",
+        "//src/main/java:analysis-exec-rules-skyframe",
+        "//src/main/java:bazel-core",
+        "//src/main/java:collect",
+        "//src/main/java:concurrent",
+        "//src/main/java:events",
+        "//src/main/java:packages",
+        "//src/main/java:util",
+        "//src/main/java:vfs",
+        "//third_party:guava",
+        "//third_party:guava-testlib",
+        "//third_party:jsr305",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
+
+java_library(
+    name = "skylark_testutil",
+    srcs = glob([
+        "com/google/devtools/build/lib/skylark/util/*.java",
+    ]),
+    deps = [
+        ":analysis_testutil",
+        ":foundations_testutil",
+        ":syntax_testutil",
+        ":testutil",
+        "//src/main/java:actions",
+        "//src/main/java:analysis-exec-rules-skyframe",
+        "//src/main/java:bazel-core",
+        "//src/main/java:collect",
+        "//src/main/java:concurrent",
+        "//src/main/java:events",
+        "//src/main/java:packages",
+        "//src/main/java:vfs",
+        "//third_party:guava",
+        "//third_party:guava-testlib",
+        "//third_party:jsr305",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
+
+java_test(
+    name = "skylark_test",
+    srcs = glob([
+        "com/google/devtools/build/lib/skylark/*.java",
+    ]),
+    args = ["com.google.devtools.build.lib.AllTests"],
+    deps = [
+        ":actions_testutil",
+        ":analysis_testutil",
+        ":foundations_testutil",
+        ":skylark_testutil",
         ":test_runner",
         ":testutil",
         "//src/main/java:actions",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java
new file mode 100644
index 0000000..c0e6ad0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java
@@ -0,0 +1,47 @@
+// Copyright 2014 Google Inc. 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.skylark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.syntax.SkylarkList;
+
+/**
+ * Tests for {@link SkylarkCommandLine}.
+ */
+public class SkylarkCommandLineTest extends SkylarkTestCase {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    scratch.file(
+        "foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])");
+  }
+
+  public void testCmdHelperAll() throws Exception {
+    Object result =
+        evalRuleContextCode(
+            createRuleContext("//foo:foo"),
+            "cmd_helper.template(set(ruleContext.files.srcs), '--%{short_path}=%{path}')");
+    SkylarkList list = (SkylarkList) result;
+    assertThat(list).containsExactly("--foo/a.txt=foo/a.txt", "--foo/b.img=foo/b.img").inOrder();
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java
new file mode 100644
index 0000000..b5b9bfe
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java
@@ -0,0 +1,68 @@
+// Copyright 2014 Google Inc. 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.skylark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+
+/**
+ * Tests for SkylarkFileset and SkylarkFileType.
+ */
+public class SkylarkFileHelperTest extends SkylarkTestCase {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    scratch.file(
+        "foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])");
+  }
+
+  @SuppressWarnings("unchecked")
+  public void testFilterPasses() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(ruleContext, "FileType(['.img']).filter(ruleContext.files.srcs)");
+    assertEquals(ActionsTestUtil.baseNamesOf((Iterable<Artifact>) result), "b.img");
+  }
+
+  public void testFilterFiltersFilesOut() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(ruleContext, "FileType(['.xyz']).filter(ruleContext.files.srcs)");
+    assertThat(((Iterable<?>) result)).isEmpty();
+  }
+
+  public void testArtifactPath() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    String result = (String) evalRuleContextCode(ruleContext, "ruleContext.files.tools[0].path");
+    assertEquals("foo/t.exe", result);
+  }
+
+  public void testArtifactShortPath() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    String result =
+        (String) evalRuleContextCode(ruleContext, "ruleContext.files.tools[0].short_path");
+    assertEquals("foo/t.exe", result);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
new file mode 100644
index 0000000..c7ea692
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -0,0 +1,430 @@
+// Copyright 2014 Google Inc. 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.skylark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.PredicateWithMessage;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.rules.SkylarkFileType;
+import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.RuleFunction;
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+import org.junit.Before;
+
+/**
+ * Tests for SkylarkRuleClassFunctions.
+ */
+public class SkylarkRuleClassFunctionsTest extends SkylarkTestCase {
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    scratch.file(
+        "foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])",
+        "genrule(name = 'bar',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = [':jl', ':gl'],",
+        "  outs = ['d.txt'])",
+        "java_library(name = 'jl',",
+        "  srcs = ['a.java'])",
+        "genrule(name = 'gl',",
+        "  cmd = 'touch $(OUTS)',",
+        "  srcs = ['a.go'],",
+        "  outs = [ 'gl.a', 'gl.gcgox', ],",
+        "  output_to_bindir = 1,",
+        ")");
+  }
+
+  public void testImplicitArgsAttribute() throws Exception {
+    eval(
+        "def _impl(ctx):",
+        "  pass",
+        "exec_rule = rule(implementation = _impl, executable = True)",
+        "non_exec_rule = rule(implementation = _impl)");
+    assertTrue(getRuleClass("exec_rule").hasAttr("args", Type.STRING_LIST));
+    assertFalse(getRuleClass("non_exec_rule").hasAttr("args", Type.STRING_LIST));
+  }
+
+  private RuleClass getRuleClass(String name) throws Exception {
+    return ((RuleFunction) lookup(name)).getBuilder().build(name);
+  }
+
+  private void registerDummyUserDefinedFunction() throws Exception {
+    eval("def impl():\n" + "  return 0\n");
+  }
+
+  public void testAttrWithOnlyType() throws Exception {
+    Object result = evalRuleClassCode("attr.string_list()");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(Type.STRING_LIST, attr.getType());
+  }
+
+  public void testOutputListAttr() throws Exception {
+    Object result = evalRuleClassCode("attr.output_list()");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(BuildType.OUTPUT_LIST, attr.getType());
+  }
+
+  public void testIntListAttr() throws Exception {
+    Object result = evalRuleClassCode("attr.int_list()");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(Type.INTEGER_LIST, attr.getType());
+  }
+
+  public void testOutputAttr() throws Exception {
+    Object result = evalRuleClassCode("attr.output()");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(BuildType.OUTPUT, attr.getType());
+  }
+
+  public void testStringDictAttr() throws Exception {
+    Object result = evalRuleClassCode("attr.string_dict(default = {'a': 'b'})");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(Type.STRING_DICT, attr.getType());
+  }
+
+  public void testAttrAllowedFileTypesAnyFile() throws Exception {
+    Object result = evalRuleClassCode("attr.label_list(allow_files = True)");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(FileTypeSet.ANY_FILE, attr.getAllowedFileTypesPredicate());
+  }
+
+  public void testAttrAllowedFileTypesWrongType() throws Exception {
+    checkErrorContains(
+        "allow_files should be a boolean or a filetype object.",
+        "attr.label_list(allow_files = ['.xml'])");
+  }
+
+  public void testAttrWithSkylarkFileType() throws Exception {
+    Object result = evalRuleClassCode("attr.label_list(allow_files = FileType(['.xml']))");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertTrue(attr.getAllowedFileTypesPredicate().apply("a.xml"));
+    assertFalse(attr.getAllowedFileTypesPredicate().apply("a.txt"));
+  }
+
+  public void testAttrWithProviders() throws Exception {
+    Object result =
+        evalRuleClassCode("attr.label_list(allow_files = True, providers = ['a', 'b'])");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(ImmutableSet.of("a", "b"), attr.getMandatoryProviders());
+  }
+
+  public void testNonLabelAttrWithProviders() throws Exception {
+    checkErrorContains(
+        "unexpected keyword 'providers' in call to string", "attr.string(providers = ['a'])");
+  }
+
+  private static final RuleClass.ConfiguredTargetFactory<Object, Object>
+      DUMMY_CONFIGURED_TARGET_FACTORY =
+          new RuleClass.ConfiguredTargetFactory<Object, Object>() {
+            @Override
+            public Object create(Object ruleContext) throws InterruptedException {
+              throw new IllegalStateException();
+            }
+          };
+
+  private RuleClass ruleClass(String name) {
+    return new RuleClass.Builder(name, RuleClassType.NORMAL, false)
+        .factory(DUMMY_CONFIGURED_TARGET_FACTORY)
+        .add(Attribute.attr("tags", Type.STRING_LIST))
+        .build();
+  }
+
+  public void testAttrAllowedRuleClassesSpecificRuleClasses() throws Exception {
+    Object result =
+        evalRuleClassCode("attr.label_list(allow_rules = ['java_binary'], allow_files = True)");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a");
+    assertTrue(attr.getAllowedRuleClassesPredicate().apply(ruleClass("java_binary")));
+    assertFalse(attr.getAllowedRuleClassesPredicate().apply(ruleClass("genrule")));
+  }
+
+  public void testAttrDefaultValue() throws Exception {
+    Object result = evalRuleClassCode("attr.string(default = 'some value')");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals("some value", attr.getDefaultValueForTesting());
+  }
+
+  public void testAttrDefaultValueBadType() throws Exception {
+    checkErrorContains(
+        "Method attr.string(*, default: string, mandatory: bool, values: sequence of strings) "
+            + "is not applicable for arguments (int, bool, list): 'default' is int, "
+            + "but should be string",
+        "attr.string(default = 1)");
+  }
+
+  public void testAttrMandatory() throws Exception {
+    Object result = evalRuleClassCode("attr.string(mandatory=True)");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertTrue(attr.isMandatory());
+    assertFalse(attr.isNonEmpty());
+  }
+
+  public void testAttrNonEmpty() throws Exception {
+    Object result = evalRuleClassCode("attr.string_list(non_empty=True)");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertTrue(attr.isNonEmpty());
+    assertFalse(attr.isMandatory());
+  }
+
+  public void testAttrBadKeywordArguments() throws Exception {
+    checkErrorContains(
+        "unexpected keyword 'bad_keyword' in call to string", "attr.string(bad_keyword = '')");
+  }
+
+  public void testAttrCfg() throws Exception {
+    Object result = evalRuleClassCode("attr.label(cfg = HOST_CFG, allow_files = True)");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    assertEquals(ConfigurationTransition.HOST, attr.getConfigurationTransition());
+  }
+
+  public void testAttrValues() throws Exception {
+    Object result = evalRuleClassCode("attr.string(values = ['ab', 'cd'])");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    PredicateWithMessage<Object> predicate = attr.getAllowedValues();
+    assertThat(predicate.apply("ab")).isTrue();
+    assertThat(predicate.apply("xy")).isFalse();
+  }
+
+  public void testAttrIntValues() throws Exception {
+    Object result = evalRuleClassCode("attr.int(values = [1, 2])");
+    Attribute attr = ((Attribute.Builder<?>) result).build("a1");
+    PredicateWithMessage<Object> predicate = attr.getAllowedValues();
+    assertThat(predicate.apply(2)).isTrue();
+    assertThat(predicate.apply(3)).isFalse();
+  }
+
+  public void testRuleImplementation() throws Exception {
+    eval("def impl(ctx): return None", "rule1 = rule(impl)");
+    RuleClass c = ((RuleFunction) lookup("rule1")).getBuilder().build("rule1");
+    assertEquals("impl", c.getConfiguredTargetFunction().getName());
+  }
+
+  public void testLateBoundAttrWorksWithOnlyLabel() throws Exception {
+    checkEvalError(
+        "Method attr.string(*, default: string, mandatory: bool, values: sequence of strings) "
+            + "is not applicable for arguments (function, bool, list): 'default' is function, "
+            + "but should be string",
+        "def attr_value(cfg): return 'a'",
+        "attr.string(default=attr_value)");
+  }
+
+  public void testRuleAddAttribute() throws Exception {
+    eval("def impl(ctx): return None", "r1 = rule(impl, attrs={'a1': attr.string()})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    assertTrue(c.hasAttr("a1", Type.STRING));
+  }
+
+  public void testOutputToGenfiles() throws Exception {
+    eval("def impl(ctx): pass", "r1 = rule(impl, output_to_genfiles=True)");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    assertFalse(c.hasBinaryOutput());
+  }
+
+  public void testRuleAddMultipleAttributes() throws Exception {
+    eval(
+        "def impl(ctx): return None",
+        "r1 = rule(impl,",
+        "     attrs = {",
+        "            'a1': attr.label_list(allow_files=True),",
+        "            'a2': attr.int()",
+        "})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    assertTrue(c.hasAttr("a1", BuildType.LABEL_LIST));
+    assertTrue(c.hasAttr("a2", Type.INTEGER));
+  }
+
+  public void testRuleAttributeFlag() throws Exception {
+    eval(
+        "def impl(ctx): return None",
+        "r1 = rule(impl, attrs = {'a1': attr.string(mandatory=True)})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    assertTrue(c.getAttributeByName("a1").isMandatory());
+  }
+
+  public void testRuleOutputs() throws Exception {
+    eval("def impl(ctx): return None", "r1 = rule(impl, outputs = {'a': 'a.txt'})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    ImplicitOutputsFunction function = c.getImplicitOutputsFunction();
+    assertEquals("a.txt", Iterables.getOnlyElement(function.getImplicitOutputs(null)));
+  }
+
+  public void testRuleUnknownKeyword() throws Exception {
+    registerDummyUserDefinedFunction();
+    checkErrorContains(
+        "unexpected keyword 'bad_keyword' in call to " + "rule(implementation: function, ",
+        "rule(impl, bad_keyword = 'some text')");
+  }
+
+  public void testRuleImplementationMissing() throws Exception {
+    checkErrorContains(
+        "missing mandatory positional argument 'implementation' while calling "
+            + "rule(implementation",
+        "rule(attrs = {})");
+  }
+
+  public void testRuleBadTypeForAdd() throws Exception {
+    registerDummyUserDefinedFunction();
+    checkErrorContains(
+        "expected dict or NoneType for 'attrs' while calling rule but got string instead: "
+            + "some text",
+        "rule(impl, attrs = 'some text')");
+  }
+
+  public void testRuleBadTypeInAdd() throws Exception {
+    registerDummyUserDefinedFunction();
+    checkErrorContains(
+        "Illegal argument: "
+            + "expected <String, Builder> type for 'attrs' but got <string, string> instead",
+        "rule(impl, attrs = {'a1': 'some text'})");
+  }
+
+  public void testLabel() throws Exception {
+    Object result = evalRuleClassCode("Label('//foo/foo:foo')");
+    assertEquals("//foo/foo:foo", ((Label) result).toString());
+  }
+
+  public void testLabelSameInstance() throws Exception {
+    Object l1 = evalRuleClassCode("Label('//foo/foo:foo')");
+    // Implicitly creates a new pkgContext and environment, yet labels should be the same.
+    Object l2 = evalRuleClassCode("Label('//foo/foo:foo')");
+    assertSame(l2, l1);
+  }
+
+  public void testLabelNameAndPackage() throws Exception {
+    Object result = evalRuleClassCode("Label('//foo/bar:baz').name");
+    assertEquals("baz", result);
+    // NB: implicitly creates a new pkgContext and environments, yet labels should be the same.
+    result = evalRuleClassCode("Label('//foo/bar:baz').package");
+    assertEquals("foo/bar", result);
+  }
+
+  public void testRuleLabelDefaultValue() throws Exception {
+    eval(
+        "def impl(ctx): return None\n"
+            + "r1 = rule(impl, attrs = {'a1': "
+            + "attr.label(default = Label('//foo:foo'), allow_files=True)})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    Attribute a = c.getAttributeByName("a1");
+    assertEquals("//foo:foo", ((Label) a.getDefaultValueForTesting()).toString());
+  }
+
+  public void testIntDefaultValue() throws Exception {
+    eval("def impl(ctx): return None", "r1 = rule(impl, attrs = {'a1': attr.int(default = 40+2)})");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    Attribute a = c.getAttributeByName("a1");
+    assertEquals(42, a.getDefaultValueForTesting());
+  }
+
+  public void testFileType() throws Exception {
+    Object result = evalRuleClassCode("FileType(['.css'])");
+    SkylarkFileType fts = (SkylarkFileType) result;
+    assertEquals(ImmutableList.of(".css"), fts.getExtensions());
+  }
+
+  public void testRuleInheritsBaseRuleAttributes() throws Exception {
+    eval("def impl(ctx): return None", "r1 = rule(impl)");
+    RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1");
+    assertTrue(c.hasAttr("tags", Type.STRING_LIST));
+    assertTrue(c.hasAttr("visibility", BuildType.NODEP_LABEL_LIST));
+    assertTrue(c.hasAttr("deprecation", Type.STRING));
+    assertTrue(c.hasAttr(":action_listener", BuildType.LABEL_LIST)); // required for extra actions
+  }
+
+  private void checkTextMessage(String from, String... lines) throws Exception {
+    Object result = evalRuleClassCode(from);
+    assertEquals(Joiner.on("\n").join(lines) + "\n", result);
+  }
+
+  public void testSimpleTextMessagesBooleanFields() throws Exception {
+    checkTextMessage("struct(name=True).to_proto()", "name: true");
+    checkTextMessage("struct(name=False).to_proto()", "name: false");
+  }
+
+  public void testSimpleTextMessages() throws Exception {
+    checkTextMessage("struct(name='value').to_proto()", "name: \"value\"");
+    checkTextMessage("struct(name=['a', 'b']).to_proto()", "name: \"a\"", "name: \"b\"");
+    checkTextMessage("struct(name=123).to_proto()", "name: 123");
+    checkTextMessage("struct(name=[1, 2, 3]).to_proto()", "name: 1", "name: 2", "name: 3");
+    checkTextMessage("struct(a=struct(b='b')).to_proto()", "a {", "  b: \"b\"", "}");
+    checkTextMessage(
+        "struct(a=[struct(b='x'), struct(b='y')]).to_proto()",
+        "a {",
+        "  b: \"x\"",
+        "}",
+        "a {",
+        "  b: \"y\"",
+        "}");
+    checkTextMessage(
+        "struct(a=struct(b=struct(c='c'))).to_proto()", "a {", "  b {", "    c: \"c\"", "  }", "}");
+  }
+
+  public void testTextMessageEscapes() throws Exception {
+    checkTextMessage("struct(name='a\"b').to_proto()", "name: \"a\\\"b\"");
+    checkTextMessage("struct(name='a\\'b').to_proto()", "name: \"a'b\"");
+    checkTextMessage("struct(name='a\\nb').to_proto()", "name: \"a\\nb\"");
+  }
+
+  public void testTextMessageInvalidElementInListStructure() throws Exception {
+    checkErrorContains(
+        "Invalid text format, expected a struct, a string, a bool, or "
+            + "an int but got a list for list element in struct field 'a'",
+        "struct(a=[['b']]).to_proto()");
+  }
+
+  public void testTextMessageInvalidStructure() throws Exception {
+    checkErrorContains(
+        "Invalid text format, expected a struct, a string, a bool, or an int "
+            + "but got a ConfigurationTransition for struct field 'a'",
+        "struct(a=DATA_CFG).to_proto()");
+  }
+
+  // Regression test for b/18352962
+  public void testLabelAttrWrongDefault() throws Exception {
+    checkErrorContains(
+        "expected Label or Label-returning function or NoneType for 'default' "
+            + "while calling label but got string instead: //foo:bar",
+        "attr.label(default = '//foo:bar')");
+  }
+
+  public void testLabelGetRelative() throws Exception {
+    assertEquals("//foo:baz", eval("Label('//foo:bar').relative('baz')").toString());
+    assertEquals("//baz:qux", eval("Label('//foo:bar').relative('//baz:qux')").toString());
+  }
+
+  public void testLabelGetRelativeSyntaxError() throws Exception {
+    checkErrorContains(
+        "invalid target name 'bad syntax': target names may not contain ' '",
+        "Label('//foo:bar').relative('bad syntax')");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
new file mode 100644
index 0000000..1a37c29
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
@@ -0,0 +1,316 @@
+// Copyright 2015 Google Inc. 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.skylark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.FileConfiguredTarget;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider;
+import com.google.devtools.build.lib.rules.python.PythonSourcesProvider;
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.List;
+
+/**
+ * Tests for SkylarkRuleContext.
+ */
+public class SkylarkRuleContextTest extends SkylarkTestCase {
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    scratch.file(
+        "foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])",
+        "genrule(name = 'foo2',",
+        "  cmd = 'dummy_cmd',",
+        "  outs = ['e.txt'])",
+        "genrule(name = 'bar',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = [':jl', ':gl'],",
+        "  outs = ['d.txt'])",
+        "java_library(name = 'jl',",
+        "  srcs = ['a.java'])",
+        "java_import(name = 'asr',",
+        "  jars = [ 'asr.jar' ],",
+        "  srcjar = 'asr-src.jar',",
+        ")",
+        "genrule(name = 'gl',",
+        "  cmd = 'touch $(OUTS)',",
+        "  srcs = ['a.go'],",
+        "  outs = [ 'gl.a', 'gl.gcgox', ],",
+        "  output_to_bindir = 1,",
+        ")");
+  }
+
+  public void testGetPrerequisiteArtifacts() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.files.srcs");
+    assertArtifactList(result, ImmutableList.of("a.txt", "b.img"));
+  }
+
+  public void disabledTestGetPrerequisiteArtifact() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.file.tools");
+    assertEquals("t.exe", ((Artifact) result).getFilename());
+  }
+
+  public void disabledTestGetPrerequisiteArtifactNoFile() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo2");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.file.srcs");
+    assertSame(Runtime.NONE, result);
+  }
+
+  private void assertArtifactList(Object result, List<String> artifacts) {
+    assertThat(result).isInstanceOf(SkylarkList.class);
+    SkylarkList resultList = (SkylarkList) result;
+    assertEquals(artifacts.size(), resultList.size());
+    int i = 0;
+    for (String artifact : artifacts) {
+      assertEquals(artifact, ((Artifact) resultList.get(i++)).getFilename());
+    }
+  }
+
+  public void testGetPrerequisites() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.srcs");
+    // Check for a known provider
+    TransitiveInfoCollection tic1 = (TransitiveInfoCollection) ((SkylarkList) result).get(0);
+    assertNotNull(tic1.getProvider(JavaSourceJarsProvider.class));
+    // Check an unimplemented provider too
+    assertNull(tic1.getProvider(PythonSourcesProvider.class));
+  }
+
+  public void testGetPrerequisite() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:asr");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.srcjar");
+    TransitiveInfoCollection tic = (TransitiveInfoCollection) result;
+    assertThat(tic).isInstanceOf(FileConfiguredTarget.class);
+    assertEquals("asr-src.jar", tic.getLabel().getName());
+  }
+
+  public void testMiddleMan() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:jl");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.middle_man(':host_jdk')");
+    assertThat(
+            Iterables.getOnlyElement(((SkylarkNestedSet) result).getSet(Artifact.class))
+                .getExecPathString())
+        .contains("middlemen");
+  }
+
+  public void testGetRuleAttributeListType() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs");
+    assertThat(result).isInstanceOf(SkylarkList.class);
+  }
+
+  public void testGetRuleAttributeListValue() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs");
+    assertEquals(1, ((SkylarkList) result).size());
+  }
+
+  public void testGetRuleAttributeListValueNoGet() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs");
+    assertEquals(1, ((SkylarkList) result).size());
+  }
+
+  public void testGetRuleAttributeStringTypeValue() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.cmd");
+    assertEquals("dummy_cmd", (String) result);
+  }
+
+  public void testGetRuleAttributeStringTypeValueNoGet() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.cmd");
+    assertEquals("dummy_cmd", (String) result);
+  }
+
+  public void testGetRuleAttributeBadAttributeName() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"), "No attribute 'bad'", "ruleContext.attr.bad");
+  }
+
+  public void testGetLabel() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.label");
+    assertEquals("//foo:foo", ((Label) result).toString());
+  }
+
+  public void testRuleError() throws Exception {
+    checkErrorContains(createRuleContext("//foo:foo"), "message", "fail('message')");
+  }
+
+  public void testAttributeError() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "attribute srcs: message",
+        "fail(attr='srcs', msg='message')");
+  }
+
+  public void testGetExecutablePrerequisite() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:jl");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.executable._ijar");
+    assertEquals("ijar", ((Artifact) result).getFilename());
+  }
+
+  public void testCreateSpawnActionArgumentsWithExecutableFilesToRunProvider() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:jl");
+    evalRuleContextCode(
+        ruleContext,
+        "ruleContext.action(\n"
+            + "  inputs = ruleContext.files.srcs,\n"
+            + "  outputs = ruleContext.files.srcs,\n"
+            + "  arguments = ['--a','--b'],\n"
+            + "  executable = ruleContext.executable._ijar)\n");
+    SpawnAction action =
+        (SpawnAction)
+            Iterables.getOnlyElement(
+                ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertThat(action.getCommandFilename()).endsWith("/ijar");
+  }
+
+  public void testOutputs() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+    Iterable<?> result = (Iterable<?>) evalRuleContextCode(ruleContext, "ruleContext.outputs.outs");
+    assertEquals("d.txt", ((Artifact) Iterables.getOnlyElement(result)).getFilename());
+  }
+
+  public void testSkylarkRuleContextStr() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "'%s' % ruleContext");
+    assertEquals("//foo:foo", result);
+  }
+
+  public void testSkylarkRuleContextGetDefaultShellEnv() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.configuration.default_shell_env");
+    assertThat(result).isInstanceOf(ImmutableMap.class);
+  }
+
+  public void testCheckPlaceholders() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(ruleContext, "ruleContext.check_placeholders('%{name}', ['name'])");
+    assertEquals(true, result);
+  }
+
+  public void testCheckPlaceholdersBadPlaceholder() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(ruleContext, "ruleContext.check_placeholders('%{name}', ['abc'])");
+    assertEquals(false, result);
+  }
+
+  public void testExpandMakeVariables() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext, "ruleContext.expand_make_variables('cmd', '$(ABC)', {'ABC': 'DEF'})");
+    assertEquals("DEF", result);
+  }
+
+  public void testExpandMakeVariablesShell() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(ruleContext, "ruleContext.expand_make_variables('cmd', '$$ABC', {})");
+    assertEquals("$ABC", result);
+  }
+
+  public void testConfiguration() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.configuration");
+    assertSame(result, ruleContext.getRuleContext().getConfiguration());
+  }
+
+  public void testHostConfiguration() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.host_configuration");
+    assertSame(result, ruleContext.getRuleContext().getHostConfiguration());
+  }
+
+  public void testWorkspaceName() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.workspace_name");
+    assertSame(result, TestConstants.WORKSPACE_NAME);
+  }
+
+  public void testDeriveArtifactLegacy() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "ruleContext.new_file(ruleContext.configuration.genfiles_dir," + "  'a/b.txt')");
+    PathFragment fragment = ((Artifact) result).getRootRelativePath();
+    assertEquals("foo/a/b.txt", fragment.getPathString());
+  }
+
+  public void testDeriveArtifact() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.new_file('a/b.txt')");
+    PathFragment fragment = ((Artifact) result).getRootRelativePath();
+    assertEquals("foo/a/b.txt", fragment.getPathString());
+  }
+
+  public void testParamFileLegacy() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "ruleContext.new_file(ruleContext.configuration.bin_dir,"
+                + "ruleContext.files.tools[0], '.params')");
+    PathFragment fragment = ((Artifact) result).getRootRelativePath();
+    assertEquals("foo/t.exe.params", fragment.getPathString());
+  }
+
+  public void testParamFile() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext, "ruleContext.new_file_suffix(ruleContext.files.tools[0], '.params')");
+    PathFragment fragment = ((Artifact) result).getRootRelativePath();
+    assertEquals("foo/t.exe.params", fragment.getPathString());
+  }
+
+  // new_file_suffix() is deprecated. This test will be removed after the next blaze release (not
+  // later than end of August, 2015).
+  public void testParamFileSuffix() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext, "ruleContext.new_file_suffix(ruleContext.files.tools[0], '.params')");
+    PathFragment fragment = ((Artifact) result).getRootRelativePath();
+    assertEquals("foo/t.exe.params", fragment.getPathString());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
new file mode 100644
index 0000000..5a69696
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
@@ -0,0 +1,810 @@
+// Copyright 2014 Google Inc. 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.skylark;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.CommandHelper;
+import com.google.devtools.build.lib.analysis.Runfiles;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.syntax.BuiltinFunction;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.syntax.SkylarkSignature;
+import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for SkylarkRuleImplementationFunctions.
+ */
+public class SkylarkRuleImplementationFunctionsTest extends SkylarkTestCase {
+
+  @SkylarkSignature(
+    name = "mock",
+    documented = false,
+    mandatoryPositionals = {@Param(name = "mandatory", doc = "")},
+    optionalPositionals = {@Param(name = "optional", doc = "")},
+    mandatoryNamedOnly = {@Param(name = "mandatory_key", doc = "")},
+    optionalNamedOnly = {@Param(name = "optional_key", doc = "", defaultValue = "'x'")}
+  )
+  private BuiltinFunction mockFunc;
+
+  /**
+   * Used for {@link #testStackTraceWithoutOriginalMessage()} and {@link
+   * #testNoStackTraceOnInterrupt}.
+   */
+  @SkylarkSignature(name = "throw", documented = false)
+  BuiltinFunction throwFunction;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    scratch.file(
+        "foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])",
+        "genrule(name = 'bar',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = [':jl', ':gl'],",
+        "  outs = ['d.txt'])",
+        "genrule(name = 'baz',",
+        "  cmd = 'dummy_cmd',",
+        "  outs = ['e.txt'])",
+        "java_library(name = 'jl',",
+        "  srcs = ['a.java'])",
+        "genrule(name = 'gl',",
+        "  cmd = 'touch $(OUTS)',",
+        "  srcs = ['a.go'],",
+        "  outs = [ 'gl.a', 'gl.gcgox', ],",
+        "  output_to_bindir = 1,",
+        ")");
+  }
+
+  private void setupSkylarkFunction(String line) throws Exception {
+    mockFunc =
+        new BuiltinFunction("mock") {
+          @SuppressWarnings("unused")
+          public Object invoke(
+              Object mandatory, Object optional, Object mandatoryKey, Object optionalKey) {
+            return EvalUtils.optionMap(
+                "mandatory",
+                mandatory,
+                "optional",
+                optional,
+                "mandatory_key",
+                mandatoryKey,
+                "optional_key",
+                optionalKey);
+          }
+        };
+    assertFalse(mockFunc.isConfigured());
+    mockFunc.configure(
+        SkylarkRuleImplementationFunctionsTest.class
+            .getDeclaredField("mockFunc")
+            .getAnnotation(SkylarkSignature.class));
+    update("mock", mockFunc);
+    eval(line);
+  }
+
+  private void checkSkylarkFunctionError(String errorMsg, String line) throws Exception {
+    try {
+      setupSkylarkFunction(line);
+      fail();
+    } catch (EvalException e) {
+      assertThat(e).hasMessage(errorMsg);
+    }
+  }
+
+  public void testSkylarkFunctionPosArgs() throws Exception {
+    setupSkylarkFunction("a = mock('a', 'b', mandatory_key='c')");
+    Map<?, ?> params = (Map<?, ?>) lookup("a");
+    assertEquals("a", params.get("mandatory"));
+    assertEquals("b", params.get("optional"));
+    assertEquals("c", params.get("mandatory_key"));
+    assertEquals("x", params.get("optional_key"));
+  }
+
+  public void testSkylarkFunctionKwArgs() throws Exception {
+    setupSkylarkFunction("a = mock(optional='b', mandatory='a', mandatory_key='c')");
+    Map<?, ?> params = (Map<?, ?>) lookup("a");
+    assertEquals("a", params.get("mandatory"));
+    assertEquals("b", params.get("optional"));
+    assertEquals("c", params.get("mandatory_key"));
+    assertEquals("x", params.get("optional_key"));
+  }
+
+  public void testSkylarkFunctionTooFewArguments() throws Exception {
+    checkSkylarkFunctionError(
+        "insufficient arguments received by mock("
+            + "mandatory, optional = None, *, mandatory_key, optional_key = \"x\") "
+            + "(got 0, expected at least 1)",
+        "mock()");
+  }
+
+  public void testSkylarkFunctionTooManyArguments() throws Exception {
+    checkSkylarkFunctionError(
+        "too many (3) positional arguments in call to "
+            + "mock(mandatory, optional = None, *, mandatory_key, optional_key = \"x\")",
+        "mock('a', 'b', 'c')");
+  }
+
+  public void testSkylarkFunctionAmbiguousArguments() throws Exception {
+    checkSkylarkFunctionError(
+        "argument 'mandatory' passed both by position and by name "
+            + "in call to mock(mandatory, optional = None, *, mandatory_key, optional_key = \"x\")",
+        "mock('by position', mandatory='by_key', mandatory_key='c')");
+  }
+
+  @SuppressWarnings("unchecked")
+  public void testListComprehensionsWithNestedSet() throws Exception {
+    Object result = eval("[x + x for x in set([1, 2, 3])]");
+    assertThat((Iterable<Object>) result).containsExactly(2, 4, 6).inOrder();
+  }
+
+  public void testNestedSetGetsConvertedToSkylarkNestedSet() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "dep = ruleContext.attr.tools[0]",
+            "provider(dep, 'analysis.FileProvider').files_to_build");
+    SkylarkNestedSet nset = (SkylarkNestedSet) result;
+    assertEquals(Artifact.class, nset.getContentType().getType());
+  }
+
+  public void testCreateSpawnActionCreatesSpawnAction() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    createTestSpawnAction(ruleContext);
+    Action action =
+        Iterables.getOnlyElement(
+            ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertThat(action).isInstanceOf(SpawnAction.class);
+  }
+
+  public void testCreateSpawnActionArgumentsWithCommand() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    createTestSpawnAction(ruleContext);
+    SpawnAction action =
+        (SpawnAction)
+            Iterables.getOnlyElement(
+                ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertArtifactFilenames(action.getInputs(), "a.txt", "b.img");
+    assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img");
+    assertContainsSublist(action.getArguments(), "-c", "dummy_command", "", "--a", "--b");
+    assertEquals("DummyMnemonic", action.getMnemonic());
+    assertEquals("dummy_message", action.getProgressMessage());
+    assertEquals(targetConfig.getDefaultShellEnvironment(), action.getEnvironment());
+  }
+
+  public void testCreateSpawnActionArgumentsWithExecutable() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    evalRuleContextCode(
+        ruleContext,
+        "ruleContext.action(",
+        "  inputs = ruleContext.files.srcs,",
+        "  outputs = ruleContext.files.srcs,",
+        "  arguments = ['--a','--b'],",
+        "  executable = ruleContext.files.tools[0])");
+    SpawnAction action =
+        (SpawnAction)
+            Iterables.getOnlyElement(
+                ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertArtifactFilenames(action.getInputs(), "a.txt", "b.img", "t.exe");
+    assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img");
+    assertContainsSublist(action.getArguments(), "foo/t.exe", "--a", "--b");
+  }
+
+  public void testCreateSpawnActionArgumentsBadExecutable() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "expected file or PathFragment for executable but got string instead",
+        "ruleContext.action(",
+        "  inputs = ruleContext.files.srcs,",
+        "  outputs = ruleContext.files.srcs,",
+        "  arguments = ['--a','--b'],",
+        "  executable = 'xyz.exe')");
+  }
+
+  public void testCreateSpawnActionShellCommandList() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    evalRuleContextCode(
+        ruleContext,
+        "ruleContext.action(",
+        "  inputs = ruleContext.files.srcs,",
+        "  outputs = ruleContext.files.srcs,",
+        "  mnemonic = 'DummyMnemonic',",
+        "  command = ['dummy_command', '--arg1', '--arg2'],",
+        "  progress_message = 'dummy_message')");
+    SpawnAction action =
+        (SpawnAction)
+            Iterables.getOnlyElement(
+                ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertThat(action.getArguments())
+        .containsExactly("dummy_command", "--arg1", "--arg2")
+        .inOrder();
+  }
+
+  public void testCreateSpawnActionEnvAndExecInfo() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    evalRuleContextCode(
+        ruleContext,
+        "env = {'a' : 'b'}",
+        "ruleContext.action(",
+        "  inputs = ruleContext.files.srcs,",
+        "  outputs = ruleContext.files.srcs,",
+        "  env = env,",
+        "  execution_requirements = env,",
+        "  mnemonic = 'DummyMnemonic',",
+        "  command = 'dummy_command',",
+        "  progress_message = 'dummy_message')");
+    SpawnAction action =
+        (SpawnAction)
+            Iterables.getOnlyElement(
+                ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions());
+    assertEquals(ImmutableMap.of("a", "b"), action.getEnvironment());
+    assertEquals(ImmutableMap.of("a", "b"), action.getExecutionInfo());
+  }
+
+  public void testCreateSpawnActionUnknownParam() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    checkErrorContains(
+        ruleContext,
+        "unexpected keyword 'bad_param' in call to action(self: ctx, *, ",
+        "ruleContext.action(outputs=[], bad_param = 'some text')");
+  }
+
+  public void testCreateSpawnActionNoExecutable() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    checkErrorContains(
+        ruleContext,
+        "You must specify either 'command' or 'executable' argument",
+        "ruleContext.action(outputs=[])");
+  }
+
+  private Object createTestSpawnAction(SkylarkRuleContext ruleContext) throws Exception {
+    return evalRuleContextCode(
+        ruleContext,
+        "ruleContext.action(",
+        "  inputs = ruleContext.files.srcs,",
+        "  outputs = ruleContext.files.srcs,",
+        "  arguments = ['--a','--b'],",
+        "  mnemonic = 'DummyMnemonic',",
+        "  command = 'dummy_command',",
+        "  progress_message = 'dummy_message',",
+        "  use_default_shell_env = True)");
+  }
+
+  public void testCreateSpawnActionBadGenericArg() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "Illegal argument in call to action: list element \"a\" is not of type File",
+        "l = ['a', 'b']",
+        "ruleContext.action(",
+        "  outputs = l,",
+        "  command = 'dummy_command')");
+  }
+
+  public void testCreateSpawnActionCommandsListTooShort() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "'command' list has to be of size at least 3",
+        "ruleContext.action(",
+        "  outputs = ruleContext.files.srcs,",
+        "  command = ['dummy_command', '--arg'])");
+  }
+
+  public void testCreateFileAction() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    FileWriteAction action =
+        (FileWriteAction)
+            evalRuleContextCode(
+                ruleContext,
+                "ruleContext.file_action(",
+                "  output = ruleContext.files.srcs[0],",
+                "  content = 'hello world',",
+                "  executable = False)");
+    assertEquals("foo/a.txt", Iterables.getOnlyElement(action.getOutputs()).getExecPathString());
+    assertEquals("hello world", action.getFileContents());
+    assertFalse(action.makeExecutable());
+  }
+
+  public void testEmptyAction() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+
+    checkEmptyAction(ruleContext, "mnemonic = 'test'");
+    checkEmptyAction(ruleContext, "mnemonic = 'test', inputs = ruleContext.files.srcs");
+
+    checkErrorContains(
+        ruleContext,
+        "missing mandatory named-only argument 'mnemonic' while calling empty_action",
+        "ruleContext.empty_action(inputs = ruleContext.files.srcs)");
+  }
+
+  private void checkEmptyAction(SkylarkRuleContext ruleContext, String namedArgs) throws Exception {
+    assertThat(
+            evalRuleContextCode(
+                ruleContext, String.format("ruleContext.empty_action(%s)", namedArgs)))
+        .isEqualTo(Runtime.NONE);
+  }
+
+  public void testEmptyActionWithExtraAction() throws Exception {
+    scratch.file(
+        "test/empty.bzl",
+        "def _impl(ctx):",
+        "  ctx.empty_action(",
+        "      inputs = ctx.files.srcs,",
+        "      mnemonic = 'EA',",
+        "  )",
+
+        "empty_action_rule = rule(",
+        "    implementation = _impl,",
+        "    attrs = {",
+        "       \"srcs\": attr.label_list(allow_files=True),",
+        "    }",
+        ")");
+
+    scratch.file(
+        "test/BUILD",
+        "load('/test/empty', 'empty_action_rule')",
+        "empty_action_rule(name = 'my_empty_action',",
+        "                srcs = ['foo.in', 'other_foo.in'])",
+
+        "action_listener(name = 'listener',",
+        "                mnemonics = ['EA'],",
+        "                extra_actions = [':extra'])",
+
+        "extra_action(name = 'extra',",
+        "             cmd='')");
+
+    getPseudoActionViaExtraAction("//test:my_empty_action", "//test:listener");
+  }
+
+  public void testExpandLocation() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+
+    // If there is only a single target, both "location" and "locations" should work
+    runExpansion(ruleContext, "location :jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+    runExpansion(ruleContext, "locations :jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+
+    runExpansion(ruleContext, "location //foo:jl", "[blaze]*-out/.*/bin/foo/libjl.jar");
+
+    // Multiple targets and "location" should result in an error
+    checkReportedErrorStartsWith(
+        ruleContext,
+        "in genrule rule //foo:bar: label '//foo:gl' "
+            + "in $(location) expression expands to more than one file, please use $(locations "
+            + "//foo:gl) instead.",
+        "ruleContext.expand_location('$(location :gl)')");
+
+    // We have to use "locations" for multiple targets
+    runExpansion(
+        ruleContext,
+        "locations :gl",
+        "[blaze]*-out/.*/bin/foo/gl.a [blaze]*-out/.*/bin/foo/gl.gcgox");
+
+    // LocationExpander just returns the input string if there is no label
+    runExpansion(ruleContext, "location", "\\$\\(location\\)");
+
+    checkReportedErrorStartsWith(
+        ruleContext,
+        "in genrule rule //foo:bar: label '//foo:abc' in $(locations) expression "
+            + "is not a declared prerequisite of this rule",
+        "ruleContext.expand_location('$(locations :abc)')");
+  }
+
+  /**
+   * Invokes ctx.expand_location() with the given parameters and checks whether this led to the
+   * expected result
+   * @param ruleContext The rule context
+   * @param command Either "location" or "locations". This only matters when the label has multiple
+   * targets
+   * @param expectedPattern Regex pattern that matches the expected result
+   */
+  private void runExpansion(SkylarkRuleContext ruleContext, String command, String expectedPattern)
+      throws Exception {
+    String expanded =
+        (String)
+            evalRuleContextCode(
+                ruleContext, String.format("ruleContext.expand_location('$(%s)')", command));
+
+    assertTrue(
+        String.format("Expanded string '%s' did not match pattern '%s'", expanded, expectedPattern),
+        Pattern.matches(expectedPattern, expanded));
+  }
+
+  public void testBadParamTypeErrorMessage() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    checkErrorContains(
+        ruleContext,
+        "Method ctx.file_action(output: File, content: string, executable: bool) is not "
+            + "applicable for arguments (File, int, bool): 'content' is int, but should be string",
+        "ruleContext.file_action(",
+        "  output = ruleContext.files.srcs[0],",
+        "  content = 1,",
+        "  executable = False)");
+  }
+
+  public void testCreateTemplateAction() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    TemplateExpansionAction action =
+        (TemplateExpansionAction)
+            evalRuleContextCode(
+                ruleContext,
+                "ruleContext.template_action(",
+                "  template = ruleContext.files.srcs[0],",
+                "  output = ruleContext.files.srcs[1],",
+                "  substitutions = {'a': 'b'},",
+                "  executable = False)");
+    assertEquals("foo/a.txt", Iterables.getOnlyElement(action.getInputs()).getExecPathString());
+    assertEquals("foo/b.img", Iterables.getOnlyElement(action.getOutputs()).getExecPathString());
+    assertEquals("a", Iterables.getOnlyElement(action.getSubstitutions()).getKey());
+    assertEquals("b", Iterables.getOnlyElement(action.getSubstitutions()).getValue());
+    assertFalse(action.makeExecutable());
+  }
+
+  /**
+   * Simulates the fact that the Parser currently uses Latin1 to read BUILD files, while users
+   * usually write those files using UTF-8 encoding.
+   * Once {@link
+   * com.google.devtools.build.lib.syntax.ParserInputSource#create(com.google.devtools.build.lib.vfs.Path)} parses files using UTF-8, this test will fail.
+   */
+  public void testCreateTemplateActionWithWrongEncoding() throws Exception {
+    String value = "Š©±½";
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    TemplateExpansionAction action =
+        (TemplateExpansionAction)
+            evalRuleContextCode(
+                ruleContext,
+                "ruleContext.template_action(",
+                "  template = ruleContext.files.srcs[0],",
+                "  output = ruleContext.files.srcs[1],",
+                "  substitutions = {'a': '" + convertUtf8ToLatin1(value) + "'},",
+                "  executable = False)");
+
+    List<Substitution> substitutions = action.getSubstitutions();
+    assertThat(substitutions).hasSize(1);
+    assertThat(substitutions.get(0).getValue()).isEqualTo(value);
+  }
+
+  /**
+   * Turns the given UTF-8 input into an "unreadable" Latin1 string
+   */
+  private String convertUtf8ToLatin1(String input) {
+    return new String(input.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
+  }
+
+  public void testGetProviderNotTransitiveInfoCollection() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "Method provider(target: Target, type: string) is not applicable for arguments "
+            + "(string, string): 'target' is string, but should be Target",
+        "provider('some string', 'FileProvider')");
+  }
+
+  public void testGetProviderNonExistingClassType() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "Unknown class type bad.Bad",
+        "def func():", // we need a func to hold the for loop
+        "  for tic in ruleContext.attr.srcs:",
+        "    provider(tic, 'bad.Bad')",
+        "func()");
+  }
+
+  public void testGetProviderNotTransitiveInfoProviderClassType() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:foo"),
+        "Not a TransitiveInfoProvider rules.java.JavaBinary",
+        "def func():", // we need a func to hold the for loop
+        "  for tic in ruleContext.attr.srcs:",
+        "    provider(tic, 'rules.java.JavaBinary')",
+        "func()");
+  }
+
+  public void testRunfilesAddFromDependencies() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+    Object result =
+        evalRuleContextCode(ruleContext, "ruleContext.runfiles(collect_default = True)");
+    assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)))
+        .contains("libjl.jar");
+  }
+
+  public void testRunfilesStatelessWorksAsOnlyPosArg() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+    Object result =
+        evalRuleContextCode(ruleContext, "ruleContext.runfiles(collect_default = True)");
+    assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)))
+        .contains("libjl.jar");
+  }
+
+  public void testRunfilesBadListGenericType() throws Exception {
+    checkErrorContains(
+        "Illegal argument in call to runfiles: list element \"some string\" is not of type File",
+        "ruleContext.runfiles(files = ['some string'])");
+  }
+
+  public void testRunfilesBadSetGenericType() throws Exception {
+    checkErrorContains(
+        "expected set of Files or NoneType for 'transitive_files' while calling runfiles "
+            + "but got set of ints instead: set([1, 2, 3])",
+        "ruleContext.runfiles(transitive_files=set([1, 2, 3]))");
+  }
+
+  public void testRunfilesArtifactsFromArtifact() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "artifacts = ruleContext.files.tools",
+            "ruleContext.runfiles(files = artifacts)");
+    assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result))).contains("t.exe");
+  }
+
+  public void testRunfilesArtifactsFromIterableArtifacts() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "artifacts = ruleContext.files.srcs",
+            "ruleContext.runfiles(files = artifacts)");
+    assertEquals(
+        ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)),
+        ImmutableList.of("a.txt", "b.img"));
+  }
+
+  public void testRunfilesArtifactsFromNestedSetArtifacts() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "ftb = set() + ruleContext.files.srcs",
+            "ruleContext.runfiles(transitive_files = ftb)");
+    assertEquals(
+        ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)),
+        ImmutableList.of("a.txt", "b.img"));
+  }
+
+  public void testRunfilesArtifactsFromDefaultAndFiles() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:bar");
+    Object result =
+        evalRuleContextCode(
+            ruleContext,
+            "artifacts = ruleContext.files.srcs",
+            // It would be nice to write [DEFAULT] + artifacts, but artifacts
+            // is an ImmutableList and Skylark interprets it as a tuple.
+            "ruleContext.runfiles(collect_default = True, files = artifacts)");
+    // From DEFAULT only libjl.jar comes, see testRunfilesAddFromDependencies().
+    assertEquals(
+        ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)),
+        ImmutableList.of("libjl.jar", "gl.a", "gl.gcgox"));
+  }
+
+  private Iterable<Artifact> getRunfileArtifacts(Object runfiles) {
+    return ((Runfiles) runfiles).getAllArtifacts();
+  }
+
+  public void testRunfilesBadKeywordArguments() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    checkErrorContains(
+        ruleContext,
+        "unexpected keyword 'bad_keyword' in call to runfiles(self: ctx, ",
+        "ruleContext.runfiles(bad_keyword = '')");
+  }
+
+  public void testCreateCommandHelper() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result = evalRuleContextCode(ruleContext, "ruleContext.command_helper([], {})");
+    assertThat(result).isInstanceOf(CommandHelper.class);
+  }
+
+  public void testNsetContainsList() throws Exception {
+    checkErrorContains(
+        "sets cannot contain items of type 'list'", "set() + [ruleContext.files.srcs]");
+  }
+
+  public void testCmdJoinPaths() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    Object result =
+        evalRuleContextCode(
+            ruleContext, "f = set(ruleContext.files.srcs)", "cmd_helper.join_paths(':', f)");
+    assertEquals("foo/a.txt:foo/b.img", result);
+  }
+
+  public void testStructPlusArtifactErrorMessage() throws Exception {
+    SkylarkRuleContext ruleContext = createRuleContext("//foo:foo");
+    checkErrorContains(
+        ruleContext,
+        "unsupported operand type(s) for +: 'File' and 'struct'",
+        "ruleContext.files.tools[0] + struct(a = 1)");
+  }
+
+  public void testNoSuchProviderErrorMessage() throws Exception {
+    checkErrorContains(
+        createRuleContext("//foo:bar"),
+        "target (rule class of 'java_library') " + "doesn't have provider 'my_provider'.",
+        "ruleContext.attr.srcs[0].my_provider");
+  }
+
+  public void testFilesForRuleConfiguredTarget() throws Exception {
+    Object result =
+        evalRuleContextCode(createRuleContext("//foo:foo"), "ruleContext.attr.srcs[0].files");
+    assertEquals(
+        "a.txt", ActionsTestUtil.baseNamesOf(((SkylarkNestedSet) result).getSet(Artifact.class)));
+  }
+
+  public void testFilesForFileConfiguredTarget() throws Exception {
+    Object result =
+        evalRuleContextCode(createRuleContext("//foo:bar"), "ruleContext.attr.srcs[0].files");
+    assertEquals(
+        "libjl.jar",
+        ActionsTestUtil.baseNamesOf(((SkylarkNestedSet) result).getSet(Artifact.class)));
+  }
+
+  public void testCtxStructFieldsCustomErrorMessages() throws Exception {
+    checkErrorContains("No attribute 'foo' in attr.", "ruleContext.attr.foo");
+    checkErrorContains("No attribute 'foo' in outputs.", "ruleContext.outputs.foo");
+    checkErrorContains("No attribute 'foo' in files.", "ruleContext.files.foo");
+    checkErrorContains("No attribute 'foo' in file.", "ruleContext.file.foo");
+    checkErrorContains("No attribute 'foo' in executable.", "ruleContext.executable.foo");
+  }
+
+  public void testBinDirPath() throws Exception {
+    SkylarkRuleContext ctx = createRuleContext("//foo:bar");
+    Object result = evalRuleContextCode(ctx, "ruleContext.configuration.bin_dir.path");
+    assertEquals(ctx.getConfiguration().getBinFragment().getPathString(), result);
+  }
+
+  public void testEmptyLabelListTypeAttrInCtx() throws Exception {
+    SkylarkRuleContext ctx = createRuleContext("//foo:baz");
+    Object result = evalRuleContextCode(ctx, "ruleContext.attr.srcs");
+    assertEquals(MutableList.EMPTY, result);
+  }
+
+  public void testDefinedMakeVariable() throws Exception {
+    SkylarkRuleContext ctx = createRuleContext("//foo:baz");
+    String javac = (String) evalRuleContextCode(ctx, "ruleContext.var['JAVAC']");
+    // Get the last path segment
+    javac = javac.substring(javac.lastIndexOf('/'));
+    assertEquals("/javac", javac);
+  }
+
+  public void testCodeCoverageConfigurationAccess() throws Exception {
+    SkylarkRuleContext ctx = createRuleContext("//foo:baz");
+    boolean coverage =
+        (Boolean) evalRuleContextCode(ctx, "ruleContext.configuration.coverage_enabled");
+    assertEquals(coverage, ctx.getRuleContext().getConfiguration().isCodeCoverageEnabled());
+  }
+
+  @Override
+  protected void checkErrorContains(String errorMsg, String... lines) throws Exception {
+    super.checkErrorContains(createRuleContext("//foo:foo"), errorMsg, lines);
+  }
+
+  /**
+   * Checks whether the given (invalid) statement leads to the expected error
+   */
+  private void checkReportedErrorStartsWith(
+      SkylarkRuleContext ruleContext, String errorMsg, String... statements) throws Exception {
+    // If the component under test relies on Reporter and EventCollector for error handling, any
+    // error would lead to an asynchronous AssertionFailedError thanks to failFastHandler in
+    // FoundationTestCase.
+    //
+    // Consequently, we disable failFastHandler and check all events for the expected error message
+    reporter.removeHandler(failFastHandler);
+
+    Object result = evalRuleContextCode(ruleContext, statements);
+
+    String first = null;
+    int count = 0;
+
+    try {
+      for (Event evt : eventCollector) {
+        if (evt.getMessage().startsWith(errorMsg)) {
+          return;
+        }
+
+        ++count;
+        first = evt.getMessage();
+      }
+
+      if (count == 0) {
+        fail(
+            String.format(
+                "checkReportedErrorStartsWith(): There was no error; the result is '%s'", result));
+      } else {
+        fail(
+            String.format(
+                "Found %d error(s), but none with the expected message '%s'. First error: '%s'",
+                count,
+                errorMsg,
+                first));
+      }
+    } finally {
+      eventCollector.clear();
+    }
+  }
+
+  public void testStackTraceWithoutOriginalMessage() throws Exception {
+    setupThrowFunction(
+        new BuiltinFunction("throw") {
+          @SuppressWarnings("unused")
+          public Object invoke() throws Exception {
+            throw new ThereIsNoMessageException();
+          }
+        });
+
+    checkEvalErrorContains(
+        "There Is No Message: SkylarkRuleImplementationFunctionsTest$2.invoke() in "
+            + "SkylarkRuleImplementationFunctionsTest.java:",
+        // This test skips the line number since it was not consistent across local tests and TAP.
+        "throw()");
+  }
+
+  public void testNoStackTraceOnInterrupt() throws Exception {
+    setupThrowFunction(
+        new BuiltinFunction("throw") {
+          @SuppressWarnings("unused")
+          public Object invoke() throws Exception {
+            throw new InterruptedException();
+          }
+        });
+    try {
+      eval("throw()");
+      fail("Expected an InterruptedException");
+    } catch (InterruptedException ex) {
+      // Expected.
+    }
+  }
+
+  private void setupThrowFunction(BuiltinFunction func) throws Exception {
+    throwFunction = func;
+    throwFunction.configure(
+        getClass().getDeclaredField("throwFunction").getAnnotation(SkylarkSignature.class));
+    update("throw", throwFunction);
+  }
+
+  private static class ThereIsNoMessageException extends EvalException {
+    public ThereIsNoMessageException() {
+      super(null, "This is not the message you are looking for."); // Unused dummy message
+    }
+
+    @Override
+    public String getMessage() {
+      return "";
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
new file mode 100644
index 0000000..11c928e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java
@@ -0,0 +1,164 @@
+// Copyright 2014 Google Inc. 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.skylark.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.PackageFactory.PackageContext;
+import com.google.devtools.build.lib.rules.SkylarkModules;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+
+import org.junit.Before;
+
+/**
+ * A class to contain the common functionality for Skylark tests.
+ */
+public abstract class SkylarkTestCase extends BuildViewTestCase {
+
+  // We don't have multiple inheritance, so we fake it.
+  protected EvaluationTestCase ev;
+
+  protected void setUpEvaluator() throws Exception {
+    ev =
+        new EvaluationTestCase() {
+          @Override
+          public Environment newEnvironment() throws Exception {
+            return Environment.builder(mutability)
+                .setSkylark()
+                .setEventHandler(getEventHandler())
+                .setGlobals(SkylarkModules.GLOBALS)
+                .setLoadingPhase()
+                .build()
+                .setupDynamic(
+                    PackageFactory.PKG_CONTEXT,
+                    // This dummy pkgContext works because no Skylark unit test attempts to actually
+                    // create rules. Creating actual rules is tested in SkylarkIntegrationTest.
+                    new PackageContext(null, null, getEventHandler()));
+          }
+        };
+    ev.setUp();
+  }
+
+  @Before
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    setUpEvaluator();
+  }
+
+  protected Object eval(String... input) throws Exception {
+    return ev.eval(input);
+  }
+
+  protected void update(String name, Object value) throws Exception {
+    ev.update(name, value);
+  }
+
+  protected Object lookup(String name) throws Exception {
+    return ev.lookup(name);
+  }
+
+  protected void checkEvalError(String msg, String... input) throws Exception {
+    ev.checkEvalError(msg, input);
+  }
+
+  protected void checkEvalErrorContains(String msg, String... input) throws Exception {
+    ev.checkEvalErrorContains(msg, input);
+  }
+
+  protected SkylarkRuleContext dummyRuleContext() throws Exception {
+    return createRuleContext("//foo:foo");
+  }
+
+  protected SkylarkRuleContext createRuleContext(String label) throws Exception {
+    return new SkylarkRuleContext(getRuleContextForSkylark(getConfiguredTarget(label)));
+  }
+
+  protected Object evalRuleContextCode(String... lines) throws Exception {
+    return evalRuleContextCode(dummyRuleContext(), lines);
+  }
+
+  /**
+   * RuleContext can't be null, SkylarkBuiltInFunctions checks it.
+   * However not all built in functions use it, if usesRuleContext == false
+   * the wrapping function won't need a ruleContext parameter.
+   */
+  protected Object evalRuleContextCode(SkylarkRuleContext ruleContext, String... code)
+      throws Exception {
+    setUpEvaluator();
+    if (ruleContext != null) {
+      update("ruleContext", ruleContext);
+    }
+    return eval(code);
+  }
+
+  protected void assertArtifactFilenames(Iterable<Artifact> artifacts, String... expected) {
+    int i = 0;
+    for (Artifact artifact : artifacts) {
+      assertEquals(expected[i++], artifact.getFilename());
+    }
+  }
+
+  protected Object evalRuleClassCode(String... lines) throws Exception {
+    setUpEvaluator();
+    return eval("def impl(ctx): return None\n" + Joiner.on("\n").join(lines));
+  }
+
+  protected void checkError(SkylarkRuleContext ruleContext, String errorMsg, String... lines)
+      throws Exception {
+    try {
+      evalRuleContextCode(ruleContext, lines);
+      fail();
+    } catch (EvalException e) {
+      assertThat(e).hasMessage(errorMsg);
+    }
+  }
+
+  protected void checkErrorStartsWith(
+      SkylarkRuleContext ruleContext, String errorMsg, String... lines) throws Exception {
+    try {
+      evalRuleContextCode(ruleContext, lines);
+      fail();
+    } catch (EvalException e) {
+      assertThat(e.getMessage()).startsWith(errorMsg);
+    }
+  }
+
+  protected void checkErrorContains(String errorMsg, String... lines) throws Exception {
+    try {
+      eval(lines);
+      fail("checkErrorContains(String, String...): There was no error");
+    } catch (EvalException e) {
+      assertThat(e.getMessage()).contains(errorMsg);
+    }
+  }
+
+  protected void checkErrorContains(
+      SkylarkRuleContext ruleContext, String errorMsg, String... lines) throws Exception {
+    try {
+      evalRuleContextCode(ruleContext, lines);
+      fail("checkErrorContains(SkylarkRuleContext, String, String...): There was no error");
+    } catch (EvalException e) {
+      assertThat(e.getMessage()).contains(errorMsg);
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java
index da709a0..5d437d6 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java
@@ -17,6 +17,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
index ecbb85b..b036e5a 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventCollector;
 import com.google.devtools.build.lib.packages.CachingPackageLocator;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.Scratch;
 import com.google.devtools.build.lib.vfs.Path;
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
index 36fc761..fd52209 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index 14b26ec..aafbe7c 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -20,6 +20,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 import com.google.devtools.build.lib.testutil.TestMode;
 
 import org.junit.Test;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
index ade0bbf..ef87be7 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index 1a11e4c..72fd205 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
index 9dc2a15..7b2759d 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.syntax.DictionaryLiteral.DictionaryEntryLiteral;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -976,7 +977,7 @@
     List<Statement> stmts3 = parseFile("[ i for (i, j, k) in [(1, 2, 3)] ]\n");
     assertThat(stmts3).hasSize(1);
   }
-  
+
   @Test
   public void testReturnNone() throws Exception {
     List<Statement> defNone = parseFileForSkylark("def foo():", "  return None\n");
@@ -993,10 +994,10 @@
       List<Statement> defNoExpr = parseFileForSkylark("def bar" + i + "():", "  return" + end);
       i++;
       assertThat(defNoExpr).hasSize(1);
-  
+
       List<Statement> bodyNoExpr = ((FunctionDefStatement) defNoExpr.get(0)).getStatements();
       assertThat(bodyNoExpr).hasSize(1);
-  
+
       ReturnStatement returnNoExpr = (ReturnStatement) bodyNoExpr.get(0);
       Identifier none = (Identifier) returnNoExpr.getReturnExpression();
       assertEquals("None", none.getName());
@@ -1149,7 +1150,7 @@
     parseFileForSkylark("load('/foo', test3 = old)\n");
     assertContainsEvent("syntax error at 'old': expected string");
   }
-  
+
   @Test
   public void testParseErrorNotComparison() throws Exception {
     setFailFast(false);
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
index 00bc347..dad7076 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
@@ -18,6 +18,7 @@
 
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
index fbceff9..1eb1338 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -175,7 +176,7 @@
   private SkylarkNestedSet get(String varname) throws Exception {
     return (SkylarkNestedSet) lookup(varname);
   }
-  
+
   @Test
   public void testSetOuterOrderWins() throws Exception {
     // The order of the outer set should define the final iteration order,
@@ -236,7 +237,7 @@
   private boolean areOrdersCompatible(Order first, Order second) {
     return first == Order.STABLE_ORDER || second == Order.STABLE_ORDER || first == second;
   }
-  
+
   @Test
   public void testSetOrderComplexUnion() throws Exception {
     // {1, 11, {2, 22}, {3, 33}, {4, 44}}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
similarity index 74%
rename from src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
rename to src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
index fd3002e..3a9c4d5 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +30,7 @@
  * Tests for the validation process of Skylark files.
  */
 @RunWith(JUnit4.class)
-public class ValidationTests extends EvaluationTestCase {
+public class ValidationTest extends EvaluationTestCase {
 
   @Test
   public void testAssignmentNotValidLValue() {
@@ -48,16 +49,14 @@
 
   @Test
   public void testTwoFunctionsWithTheSameName() throws Exception {
-    checkError("Variable foo is read only",
-        "def foo():",
-        "  return 1",
-        "def foo(x, y):",
-        "  return 1");
+    checkError(
+        "Variable foo is read only", "def foo():", "  return 1", "def foo(x, y):", "  return 1");
   }
 
   @Test
   public void testFunctionLocalVariable() throws Exception {
-    checkError("name 'a' is not defined",
+    checkError(
+        "name 'a' is not defined",
         "def func2(b):",
         "  c = b",
         "  c = a",
@@ -68,29 +67,17 @@
 
   @Test
   public void testFunctionLocalVariableDoesNotEffectGlobalValidationEnv() throws Exception {
-    checkError("name 'a' is not defined",
-        "def func1():",
-        "  a = 1",
-        "def func2(b):",
-        "  b = a");
+    checkError("name 'a' is not defined", "def func1():", "  a = 1", "def func2(b):", "  b = a");
   }
 
   @Test
   public void testFunctionParameterDoesNotEffectGlobalValidationEnv() throws Exception {
-    checkError("name 'a' is not defined",
-        "def func1(a):",
-        "  return a",
-        "def func2():",
-        "  b = a");
+    checkError("name 'a' is not defined", "def func1(a):", "  return a", "def func2():", "  b = a");
   }
 
   @Test
   public void testLocalValidationEnvironmentsAreSeparated() throws Exception {
-    parse(
-        "def func1():",
-        "  a = 1",
-        "def func2():",
-        "  a = 'abc'\n");
+    parse("def func1():", "  a = 1", "def func2():", "  a = 'abc'\n");
   }
 
   @Test
@@ -100,51 +87,41 @@
 
   @Test
   public void testSkylarkGlobalVariablesAreReadonly() throws Exception {
-    checkError("Variable a is read only",
-        "a = 1",
-        "a = 2");
+    checkError("Variable a is read only", "a = 1", "a = 2");
   }
 
   @Test
   public void testFunctionDefRecursion() throws Exception {
-    parse(
-        "def func():",
-        "  func()\n");
+    parse("def func():", "  func()\n");
   }
 
   @Test
   public void testMutualRecursion() throws Exception {
-    parse(
-        "def foo(i):",
-        "  bar(i)",
-        "def bar(i):",
-        "  foo(i)",
-        "foo(4)");
+    parse("def foo(i):", "  bar(i)", "def bar(i):", "  foo(i)", "foo(4)");
   }
 
   @Test
   public void testFunctionDefinedBelow() {
-    parse(
-        "def bar(): a = foo() + 'a'",
-        "def foo(): return 1\n");
+    parse("def bar(): a = foo() + 'a'", "def foo(): return 1\n");
   }
 
   @Test
   public void testFunctionDoesNotExist() {
-    checkError("function 'foo' does not exist",
-        "def bar(): a = foo() + 'a'");
+    checkError("function 'foo' does not exist", "def bar(): a = foo() + 'a'");
   }
 
   @Test
   public void testStructMembersAreImmutable() {
-    checkError("can only assign to variables and tuples, not to 's.x'",
+    checkError(
+        "can only assign to variables and tuples, not to 's.x'",
         "s = struct(x = 'a')",
         "s.x = 'b'\n");
   }
 
   @Test
   public void testStructDictMembersAreImmutable() {
-    checkError("can only assign to variables and tuples, not to 's.x['b']'",
+    checkError(
+        "can only assign to variables and tuples, not to 's.x['b']'",
         "s = struct(x = {'a' : 1})",
         "s.x['b'] = 2\n");
   }
@@ -161,10 +138,7 @@
 
   @Test
   public void testNoneAssignment() throws Exception {
-    parse("def func():",
-        "  a = None",
-        "  a = 2",
-        "  a = None\n");
+    parse("def func():", "  a = None", "  a = 2", "  a = None\n");
   }
 
   @Test
@@ -180,7 +154,8 @@
 
   @Test
   public void testFuncReturningDictAssignmentAsLValue() throws Exception {
-    checkError("can only assign to variables and tuples, not to 'my_dict()['b']'",
+    checkError(
+        "can only assign to variables and tuples, not to 'my_dict()['b']'",
         "def my_dict():",
         "  return {'a': 1}",
         "def func():",
@@ -190,22 +165,18 @@
 
   @Test
   public void testEmptyLiteralGenericIsSetInLaterConcatWorks() {
-    parse("def func():",
-        "  s = {}",
-        "  s['a'] = 'b'\n");
+    parse("def func():", "  s = {}", "  s['a'] = 'b'\n");
   }
 
   @Test
   public void testReadOnlyWorksForSimpleBranching() {
-    parse("if 1:",
-        "  v = 'a'",
-        "else:",
-        "  v = 'b'");
+    parse("if 1:", "  v = 'a'", "else:", "  v = 'b'");
   }
 
   @Test
   public void testReadOnlyWorksForNestedBranching() {
-    parse("if 1:",
+    parse(
+        "if 1:",
         "  if 0:",
         "    v = 'a'",
         "  else:",
@@ -219,58 +190,43 @@
 
   @Test
   public void testReadOnlyWorksForDifferentLevelBranches() {
-    checkError("Variable v is read only",
-        "if 1:",
-        "  if 1:",
-        "    v = 'a'",
-        "  v = 'b'\n");
+    checkError("Variable v is read only", "if 1:", "  if 1:", "    v = 'a'", "  v = 'b'\n");
   }
 
   @Test
   public void testReadOnlyWorksWithinSimpleBranch() {
-    checkError("Variable v is read only",
-        "if 1:",
-        "  v = 'a'",
-        "else:",
-        "  v = 'b'",
-        "  v = 'c'\n");
+    checkError(
+        "Variable v is read only", "if 1:", "  v = 'a'", "else:", "  v = 'b'", "  v = 'c'\n");
   }
 
   @Test
   public void testReadOnlyWorksWithinNestedBranch() {
-    checkError("Variable v is read only",
+    checkError(
+        "Variable v is read only",
         "if 1:",
-      "  v = 'a'",
-      "else:",
-      "  if 1:",
-      "    v = 'b'",
-      "  else:",
-      "    v = 'c'",
-      "    v = 'd'\n");
+        "  v = 'a'",
+        "else:",
+        "  if 1:",
+        "    v = 'b'",
+        "  else:",
+        "    v = 'c'",
+        "    v = 'd'\n");
   }
 
   @Test
   public void testReadOnlyWorksAfterSimpleBranch() {
-    checkError("Variable v is read only",
-        "if 1:",
-      "  v = 'a'",
-      "else:",
-      "  w = 'a'",
-      "v = 'b'");
+    checkError("Variable v is read only", "if 1:", "  v = 'a'", "else:", "  w = 'a'", "v = 'b'");
   }
 
   @Test
   public void testReadOnlyWorksAfterNestedBranch() {
-    checkError("Variable v is read only",
-        "if 1:",
-        "  if 1:",
-        "    v = 'a'",
-        "v = 'b'");
+    checkError("Variable v is read only", "if 1:", "  if 1:", "    v = 'a'", "v = 'b'");
   }
 
   @Test
   public void testReadOnlyWorksAfterNestedBranch2() {
-    checkError("Variable v is read only",
+    checkError(
+        "Variable v is read only",
         "if 1:",
         "  v = 'a'",
         "else:",
@@ -281,20 +237,17 @@
 
   @Test
   public void testModulesReadOnlyInFuncDefBody() {
-    parse("def func():",
-        "  cmd_helper = set()");
+    parse("def func():", "  cmd_helper = set()");
   }
 
   @Test
   public void testBuiltinGlobalFunctionsReadOnlyInFuncDefBody() {
-    parse("def func():",
-        "  rule = 'abc'");
+    parse("def func():", "  rule = 'abc'");
   }
 
   @Test
   public void testBuiltinGlobalFunctionsReadOnlyAsFuncDefArg() {
-    parse("def func(rule):",
-        "  return rule");
+    parse("def func(rule):", "  return rule");
   }
 
   @Test
@@ -327,19 +280,17 @@
 
   @Test
   public void testLoadRelativePathMultipleSegments() throws Exception {
-    checkError("Path 'pkg/extension.bzl' is not valid. It should either start with "
-        + "a slash or refer to a file in the current directory.",
+    checkError(
+        "Path 'pkg/extension.bzl' is not valid. It should either start with "
+            + "a slash or refer to a file in the current directory.",
         "load('pkg/extension', 'a')\n");
   }
 
   @Test
   public void testDollarErrorDoesNotLeak() throws Exception {
     setFailFast(false);
-    parseFile("def GenerateMapNames():",
-        "  a = 2",
-        "  b = [3, 4]",
-        "  if a not b:",
-        "    print(a)");
+    parseFile(
+        "def GenerateMapNames():", "  a = 2", "  b = [3, 4]", "  if a not b:", "    print(a)");
     assertContainsEvent("syntax error at 'b': expected in");
     // Parser uses "$error" symbol for error recovery.
     // It should not be used in error messages.
@@ -364,14 +315,14 @@
     assertThat(EvalUtils.getParentWithSkylarkModule(tupleClass)).isEqualTo(Tuple.class);
 
     // TODO(bazel-team): fix that?
-    assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class))
-        .isFalse();
+    assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class)).isFalse();
     assertThat(ClassObject.SkylarkClassObject.class.isAnnotationPresent(SkylarkModule.class))
         .isTrue();
-    assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class)
-        == ClassObject.SkylarkClassObject.class).isTrue();
-    assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class))
-        .isNull();
+    assertThat(
+            EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class)
+                == ClassObject.SkylarkClassObject.class)
+        .isTrue();
+    assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class)).isNull();
   }
 
   @Test
@@ -386,20 +337,16 @@
     assertThat(SkylarkType.of(tupleClass)).isEqualTo(SkylarkType.TUPLE);
     assertThat(SkylarkType.TUPLE).isNotEqualTo(SkylarkType.LIST);
 
-
     // Also for ClassObject
-    assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class))
-        .isEqualTo(SkylarkType.STRUCT);
+    assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class)).isEqualTo(SkylarkType.STRUCT);
     // TODO(bazel-team): fix that?
-    assertThat(SkylarkType.of(ClassObject.class))
-        .isNotEqualTo(SkylarkType.STRUCT);
+    assertThat(SkylarkType.of(ClassObject.class)).isNotEqualTo(SkylarkType.STRUCT);
 
     // Also test for these bazel classes, to avoid some regression.
     // TODO(bazel-team): move to some other place to remove dependency of syntax tests on Artifact?
     assertThat(SkylarkType.of(Artifact.SpecialArtifact.class))
         .isEqualTo(SkylarkType.of(Artifact.class));
-    assertThat(SkylarkType.of(RuleConfiguredTarget.class))
-        .isNotEqualTo(SkylarkType.STRUCT);
+    assertThat(SkylarkType.of(RuleConfiguredTarget.class)).isNotEqualTo(SkylarkType.STRUCT);
   }
 
   @Test
@@ -411,15 +358,16 @@
     SkylarkType combo1 = SkylarkType.Combination.of(SkylarkType.LIST, SkylarkType.INT);
     assertThat(SkylarkType.LIST.includes(combo1)).isTrue();
 
-    SkylarkType union1 = SkylarkType.Union.of(
-        SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT);
+    SkylarkType union1 =
+        SkylarkType.Union.of(SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT);
     assertThat(union1.includes(SkylarkType.MAP)).isTrue();
     assertThat(union1.includes(SkylarkType.STRUCT)).isTrue();
     assertThat(union1.includes(combo1)).isTrue();
     assertThat(union1.includes(SkylarkType.STRING)).isFalse();
 
-    SkylarkType union2 = SkylarkType.Union.of(
-        SkylarkType.LIST, SkylarkType.MAP, SkylarkType.STRING, SkylarkType.INT);
+    SkylarkType union2 =
+        SkylarkType.Union.of(
+            SkylarkType.LIST, SkylarkType.MAP, SkylarkType.STRING, SkylarkType.INT);
     SkylarkType inter1 = SkylarkType.intersection(union1, union2);
     assertThat(inter1.includes(SkylarkType.MAP)).isTrue();
     assertThat(inter1.includes(SkylarkType.LIST)).isTrue();
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
similarity index 92%
rename from src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java
rename to src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
index 553385d..be5317d 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java
@@ -11,11 +11,12 @@
 // 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;
+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.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.truth.Ordered;
 import com.google.devtools.build.lib.events.Event;
@@ -24,6 +25,13 @@
 import com.google.devtools.build.lib.events.EventKind;
 import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
 import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Expression;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.Parser;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.Statement;
 import com.google.devtools.build.lib.testutil.TestMode;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 
@@ -43,7 +51,7 @@
   protected Environment env;
   protected Mutability mutability = Mutability.create("test");
 
-  public EvaluationTestCase()   {
+  public EvaluationTestCase() {
     createNewInfrastructure();
   }
 
@@ -88,12 +96,12 @@
     if (testMode == null) {
       throw new IllegalArgumentException(
           "TestMode is null. Please set a Testmode via setMode() or set the "
-          + "Environment manually by overriding newEnvironment()");
+              + "Environment manually by overriding newEnvironment()");
     }
     return testMode.createEnvironment(getEventHandler(), null);
   }
 
-  protected void createNewInfrastructure()  {
+  protected void createNewInfrastructure() {
     eventCollectionApparatus = new EventCollectionApparatus(EventKind.ALL_EVENTS);
     factory = new PackageFactory(TestRuleClassProvider.getRuleClassProvider());
   }
@@ -137,10 +145,10 @@
   }
 
   /** Parses an Expression from string without a supporting file */
-  Expression parseExpression(String... input) {
+  @VisibleForTesting
+  public Expression parseExpression(String... input) {
     return Parser.parseExpression(
-        ParserInputSource.create(Joiner.on("\n").join(input), null),
-        getEventHandler());
+        ParserInputSource.create(Joiner.on("\n").join(input), null), getEventHandler());
   }
 
   public EvaluationTestCase update(String varname, Object value) throws Exception {
@@ -189,24 +197,30 @@
     eventCollectionApparatus.setFailFast(failFast);
     return this;
   }
+
   public EvaluationTestCase assertNoEvents() {
     eventCollectionApparatus.assertNoEvents();
     return this;
   }
+
   public EventCollector getEventCollector() {
     return eventCollectionApparatus.collector();
   }
+
   public Event assertContainsEvent(String expectedMessage) {
     return eventCollectionApparatus.assertContainsEvent(expectedMessage);
   }
-  public List<Event> assertContainsEventWithFrequency(String expectedMessage,
-      int expectedFrequency) {
+
+  public List<Event> assertContainsEventWithFrequency(
+      String expectedMessage, int expectedFrequency) {
     return eventCollectionApparatus.assertContainsEventWithFrequency(
         expectedMessage, expectedFrequency);
   }
+
   public Event assertContainsEventWithWordsInQuotes(String... words) {
     return eventCollectionApparatus.assertContainsEventWithWordsInQuotes(words);
   }
+
   public EvaluationTestCase clearEvents() {
     eventCollectionApparatus.collector().clear();
     return this;
@@ -347,8 +361,8 @@
      * @param exactMatch If true, the error message has to be identical to the expected error
      * @return An instance of Testable that runs the error check
      */
-    protected Testable errorTestable(final boolean exactMatch, final String error,
-        final String... statements) {
+    protected Testable errorTestable(
+        final boolean exactMatch, final String error, final String... statements) {
       return new Testable() {
         @Override
         public void run() throws Exception {
@@ -479,12 +493,13 @@
      * @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);
-        }
-      });
+      setup.add(
+          new Testable() {
+            @Override
+            public void run() throws Exception {
+              EvaluationTestCase.this.update(name, value);
+            }
+          });
     }
 
     /**
@@ -493,12 +508,13 @@
      * @param statements
      */
     public void registerEval(final String... statements) {
-      setup.add(new Testable() {
-        @Override
-        public void run() throws Exception {
-          EvaluationTestCase.this.eval(statements);
-        }
-      });
+      setup.add(
+          new Testable() {
+            @Override
+            public void run() throws Exception {
+              EvaluationTestCase.this.eval(statements);
+            }
+          });
     }
 
     /**
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
index dfcbfcd..b94cf17 100644
--- a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
+++ b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
@@ -44,6 +44,11 @@
   public static final String RUNFILES_PREFIX = "DOES-NOT-WORK-YET";
 
   /**
+   * Default workspace name.
+   */
+  public static final String WORKSPACE_NAME = "";
+
+  /**
    * Name of a class with an INSTANCE field of type AnalysisMock to be used for analysis tests.
    */
   public static final String TEST_ANALYSIS_MOCK =