Open source all the tests under lib/syntax/.
--
MOS_MIGRATED_REVID=87244284
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
new file mode 100644
index 0000000..f7ed359
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -0,0 +1,492 @@
+// 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.syntax;
+
+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.common.collect.Lists;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test of evaluation behavior. (Implicitly uses lexer + parser.)
+ */
+public class EvaluationTest extends AbstractEvaluationTestCase {
+
+ protected Environment env;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ PackageFactory factory = new PackageFactory(TestRuleClassProvider.getRuleClassProvider());
+ env = factory.getEnvironment();
+ }
+
+ public Environment singletonEnv(String id, Object value) {
+ Environment env = new Environment();
+ env.update(id, value);
+ return env;
+ }
+
+ @Override
+ public Object eval(String input) throws Exception {
+ return eval(parseExpr(input), env);
+ }
+
+ public void testExprs() throws Exception {
+ assertEquals("fooxbar",
+ eval("'%sx' % 'foo' + 'bar'"));
+ assertEquals("fooxbar",
+ eval("('%sx' % 'foo') + 'bar'"));
+ assertEquals("foobarx",
+ eval("'%sx' % ('foo' + 'bar')"));
+ assertEquals(579,
+ eval("123 + 456"));
+ assertEquals(333,
+ eval("456 - 123"));
+ assertEquals(2,
+ eval("8 % 3"));
+
+ checkEvalError("3 % 'foo'", "unsupported operand type(s) for %: 'int' and 'string'");
+ }
+
+ public void testListExprs() throws Exception {
+ assertEquals(Arrays.asList(1, 2, 3),
+ eval("[1, 2, 3]"));
+ assertEquals(Arrays.asList(1, 2, 3),
+ eval("(1, 2, 3)"));
+ }
+
+ public void testStringFormatMultipleArgs() throws Exception {
+ assertEquals("XYZ", eval("'%sY%s' % ('X', 'Z')"));
+ }
+
+ public void testAndOr() throws Exception {
+ assertEquals(8, eval("8 or 9"));
+ assertEquals(8, eval("8 or foo")); // check that 'foo' is not evaluated
+ assertEquals(9, eval("0 or 9"));
+ assertEquals(9, eval("8 and 9"));
+ assertEquals(0, eval("0 and 9"));
+ assertEquals(0, eval("0 and foo")); // check that 'foo' is not evaluated
+
+ assertEquals(2, eval("1 and 2 or 3"));
+ assertEquals(3, eval("0 and 2 or 3"));
+ assertEquals(3, eval("1 and 0 or 3"));
+
+ assertEquals(1, eval("1 or 2 and 3"));
+ assertEquals(3, eval("0 or 2 and 3"));
+ assertEquals(0, eval("0 or 0 and 3"));
+ assertEquals(1, eval("1 or 0 and 3"));
+ assertEquals(1, eval("1 or 0 and 3"));
+
+ assertEquals(9, eval("\"\" or 9"));
+ assertEquals("abc", eval("\"abc\" or 9"));
+ assertEquals(Environment.NONE, eval("None and 1"));
+ }
+
+ public void testNot() throws Exception {
+ assertEquals(false, eval("not 1"));
+ assertEquals(true, eval("not ''"));
+ }
+
+ public void testNotWithLogicOperators() throws Exception {
+ assertEquals(0, eval("0 and not 0"));
+ assertEquals(0, eval("not 0 and 0"));
+
+ assertEquals(true, eval("1 and not 0"));
+ assertEquals(true, eval("not 0 or 0"));
+
+ assertEquals(0, eval("not 1 or 0"));
+ assertEquals(1, eval("not 1 or 1"));
+
+ assertEquals(true, eval("not (0 and 0)"));
+ assertEquals(false, eval("not (1 or 0)"));
+ }
+
+ public void testNotWithArithmeticOperators() throws Exception {
+ assertEquals(true, eval("not 0 + 0"));
+ assertEquals(false, eval("not 2 - 1"));
+ }
+
+ public void testNotWithCollections() throws Exception {
+ assertEquals(true, eval("not []"));
+ assertEquals(false, eval("not {'a' : 1}"));
+ }
+
+ public void testEquality() throws Exception {
+ assertEquals(true, eval("1 == 1"));
+ assertEquals(false, eval("1 == 2"));
+ assertEquals(true, eval("'hello' == 'hel' + 'lo'"));
+ assertEquals(false, eval("'hello' == 'bye'"));
+ assertEquals(true, eval("[1, 2] == [1, 2]"));
+ assertEquals(false, eval("[1, 2] == [2, 1]"));
+ assertEquals(true, eval("None == None"));
+ }
+
+ public void testInequality() throws Exception {
+ assertEquals(false, eval("1 != 1"));
+ assertEquals(true, eval("1 != 2"));
+ assertEquals(false, eval("'hello' != 'hel' + 'lo'"));
+ assertEquals(true, eval("'hello' != 'bye'"));
+ assertEquals(false, eval("[1, 2] != [1, 2]"));
+ assertEquals(true, eval("[1, 2] != [2, 1]"));
+ }
+
+ public void testEqualityPrecedence() throws Exception {
+ assertEquals(true, eval("1 + 3 == 2 + 2"));
+ assertEquals(true, eval("not 1 == 2"));
+ assertEquals(false, eval("not 1 != 2"));
+ assertEquals(true, eval("2 and 3 == 3 or 1"));
+ assertEquals(2, eval("2 or 3 == 3 and 1"));
+ }
+
+ public void testLessThan() throws Exception {
+ assertEquals(true, eval("1 <= 1"));
+ assertEquals(false, eval("1 < 1"));
+ assertEquals(true, eval("'a' <= 'b'"));
+ assertEquals(false, eval("'c' < 'a'"));
+ }
+
+ public void testGreaterThan() throws Exception {
+ assertEquals(true, eval("1 >= 1"));
+ assertEquals(false, eval("1 > 1"));
+ assertEquals(false, eval("'a' >= 'b'"));
+ assertEquals(true, eval("'c' > 'a'"));
+ }
+
+ public void testCompareStringInt() throws Exception {
+ checkEvalError("'a' >= 1", "Cannot compare string with int");
+ }
+
+ public void testNotComparable() throws Exception {
+ checkEvalError("[1, 2] < [1, 3]", "[1, 2] is not comparable");
+ }
+
+ public void testSumFunction() throws Exception {
+ Function sum = new AbstractFunction("sum") {
+ @Override
+ public Object call(List<Object> args, Map<String, Object> kwargs,
+ FuncallExpression ast, Environment env) {
+ int sum = 0;
+ for (Object arg : args) {
+ sum += (Integer) arg;
+ }
+ return sum;
+ }
+ };
+
+ Environment env = singletonEnv(sum.getName(), sum);
+
+ String callExpr = "sum(1, 2, 3, 4, 5, 6)";
+ assertEquals(21, eval(callExpr, env));
+
+ assertEquals(sum, eval("sum", env));
+
+ assertEquals(0, eval("sum(a=1, b=2)", env));
+
+ // rebind 'sum' in a new environment:
+ env = new Environment();
+ exec(parseStmt("sum = 123456"), env);
+
+ assertEquals(123456, env.lookup("sum"));
+
+ // now we can't call it any more:
+ checkEvalError(callExpr, env, "'int' object is not callable");
+
+ assertEquals(123456, eval("sum", env));
+ }
+
+ public void testKeywordArgs() throws Exception {
+
+ // This function returns the list of keyword-argument keys or values,
+ // depending on whether its first (integer) parameter is zero.
+ Function keyval = new AbstractFunction("keyval") {
+ @Override
+ public Object call(List<Object> args,
+ final Map<String, Object> kwargs,
+ FuncallExpression ast,
+ Environment env) {
+ ArrayList<String> keys = new ArrayList<>(kwargs.keySet());
+ Collections.sort(keys);
+ if ((Integer) args.get(0) == 0) {
+ return keys;
+ } else {
+ return Lists.transform(keys, new com.google.common.base.Function<String, Object> () {
+ @Override public Object apply (String s) {
+ return kwargs.get(s);
+ }});
+ }
+ }
+ };
+
+ Environment env = singletonEnv(keyval.getName(), keyval);
+
+ assertEquals(eval("['bar', 'foo', 'wiz']"),
+ eval("keyval(0, foo=1, bar='bar', wiz=[1,2,3])", env));
+
+ assertEquals(eval("['bar', 1, [1,2,3]]"),
+ eval("keyval(1, foo=1, bar='bar', wiz=[1,2,3])", env));
+ }
+
+ public void testMult() throws Exception {
+ assertEquals(42, eval("6 * 7"));
+
+ assertEquals("ababab", eval("3 * 'ab'"));
+ assertEquals("", eval("0 * 'ab'"));
+ assertEquals("100000", eval("'1' + '0' * 5"));
+ }
+
+ public void testConcatStrings() throws Exception {
+ assertEquals("foobar", eval("'foo' + 'bar'"));
+ }
+
+ public void testConcatLists() throws Exception {
+ // list
+ Object x = eval("[1,2] + [3,4]");
+ assertEquals(Arrays.asList(1, 2, 3, 4), x);
+ assertFalse(EvalUtils.isImmutable(x));
+
+ // tuple
+ x = eval("(1,2) + (3,4)");
+ assertEquals(Arrays.asList(1, 2, 3, 4), x);
+ assertTrue(EvalUtils.isImmutable(x));
+
+ checkEvalError("(1,2) + [3,4]", // list + tuple
+ "can only concatenate list (not \"tuple\") to list");
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testListComprehensions() throws Exception {
+ Iterable<Object> eval = (Iterable<Object>) eval(
+ "['foo/%s.java' % x for x in []]");
+ assertThat(eval).isEmpty();
+
+ eval = (Iterable<Object>) eval(
+ "['foo/%s.java' % x for x in ['bar', 'wiz', 'quux']]");
+ assertThat(eval).containsExactly("foo/bar.java", "foo/wiz.java", "foo/quux.java").inOrder();
+
+ eval = (Iterable<Object>) eval(
+ "['%s/%s.java' % (x, y) "
+ + "for x in ['foo', 'bar'] "
+ + "for y in ['baz', 'wiz', 'quux']]");
+ assertThat(eval).containsExactly("foo/baz.java", "foo/wiz.java", "foo/quux.java",
+ "bar/baz.java", "bar/wiz.java", "bar/quux.java").inOrder();
+
+ eval = (Iterable<Object>) eval(
+ "['%s/%s.java' % (x, x) "
+ + "for x in ['foo', 'bar'] "
+ + "for x in ['baz', 'wiz', 'quux']]");
+ assertThat(eval).containsExactly("baz/baz.java", "wiz/wiz.java", "quux/quux.java",
+ "baz/baz.java", "wiz/wiz.java", "quux/quux.java").inOrder();
+
+ eval = (Iterable<Object>) eval(
+ "['%s/%s.%s' % (x, y, z) "
+ + "for x in ['foo', 'bar'] "
+ + "for y in ['baz', 'wiz', 'quux'] "
+ + "for z in ['java', 'cc']]");
+ assertThat(eval).containsExactly("foo/baz.java", "foo/baz.cc", "foo/wiz.java", "foo/wiz.cc",
+ "foo/quux.java", "foo/quux.cc", "bar/baz.java", "bar/baz.cc", "bar/wiz.java", "bar/wiz.cc",
+ "bar/quux.java", "bar/quux.cc").inOrder();
+ }
+
+ // TODO(bazel-team): should this test work in Skylark?
+ @SuppressWarnings("unchecked")
+ public void testListComprehensionModifiesGlobalEnv() throws Exception {
+ Environment env = singletonEnv("x", 42);
+ assertThat((Iterable<Object>) eval(parseExpr("[x + 1 for x in [1,2,3]]"), env))
+ .containsExactly(2, 3, 4).inOrder();
+ assertEquals(3, env.lookup("x")); // (x is global)
+ }
+
+ public void testDictComprehensions() throws Exception {
+ assertEquals(Collections.emptyMap(), eval("{x : x for x in []}"));
+ assertEquals(ImmutableMap.of(1, 1, 2, 2), eval("{x : x for x in [1, 2]}"));
+ assertEquals(ImmutableMap.of("a", "v_a", "b", "v_b"),
+ eval("{x : 'v_' + x for x in ['a', 'b']}"));
+ assertEquals(ImmutableMap.of("k_a", "a", "k_b", "b"),
+ eval("{'k_' + x : x for x in ['a', 'b']}"));
+ assertEquals(ImmutableMap.of("k_a", "v_a", "k_b", "v_b"),
+ eval("{'k_' + x : 'v_' + x for x in ['a', 'b']}"));
+ }
+
+ public void testDictComprehensions_MultipleKey() throws Exception {
+ assertEquals(ImmutableMap.of(1, 1, 2, 2), eval("{x : x for x in [1, 2, 1]}"));
+ assertEquals(ImmutableMap.of("ab", "ab", "c", "c"),
+ eval("{x : x for x in ['ab', 'c', 'a' + 'b']}"));
+ }
+
+ public void testDictComprehensions_ToString() throws Exception {
+ assertEquals("{x: x for x in [1, 2]}", parseExpr("{x : x for x in [1, 2]}").toString());
+ assertEquals("{x + 'a': x for x in [1, 2]}",
+ parseExpr("{x + 'a' : x for x in [1, 2]}").toString());
+ }
+
+ public void testListConcatenation() throws Exception {
+ assertEquals(Arrays.asList(1, 2, 3, 4), eval("[1, 2] + [3, 4]", env));
+ assertEquals(ImmutableList.of(1, 2, 3, 4), eval("(1, 2) + (3, 4)", env));
+ checkEvalError("[1, 2] + (3, 4)", "can only concatenate tuple (not \"list\") to tuple");
+ checkEvalError("(1, 2) + [3, 4]", "can only concatenate list (not \"tuple\") to list");
+ }
+
+ public void testListComprehensionFailsOnNonSequence() throws Exception {
+ checkEvalError("[x + 1 for x in 123]", "type 'int' is not an iterable");
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testListComprehensionOnString() throws Exception {
+ assertThat((Iterable<Object>) eval("[x for x in 'abc']")).containsExactly("a", "b", "c")
+ .inOrder();
+ }
+
+ public void testInvalidAssignment() throws Exception {
+ Environment env = singletonEnv("x", 1);
+ checkEvalError(parseStmt("x + 1 = 2"), env, "can only assign to variables, not to 'x + 1'");
+ }
+
+ public void testListComprehensionOnDictionary() throws Exception {
+ List<Statement> input = parseFile("val = ['var_' + n for n in {'a':1,'b':2}]");
+ exec(input, env);
+ Iterable<?> result = (Iterable<?>) env.lookup("val");
+ assertThat(result).hasSize(2);
+ assertEquals("var_a", Iterables.get(result, 0));
+ assertEquals("var_b", Iterables.get(result, 1));
+ }
+
+ public void testListComprehensionOnDictionaryCompositeExpression() throws Exception {
+ exec(parseFile("d = {1:'a',2:'b'}\n"
+ + "l = [d[x] for x in d]"), env);
+ assertEquals("[a, b]", env.lookup("l").toString());
+ }
+
+ public void testInOnListContains() throws Exception {
+ assertEquals(Boolean.TRUE, eval("'b' in ['a', 'b']"));
+ }
+
+ public void testInOnListDoesNotContain() throws Exception {
+ assertEquals(Boolean.FALSE, eval("'c' in ['a', 'b']"));
+ }
+
+ public void testInOnTupleContains() throws Exception {
+ assertEquals(Boolean.TRUE, eval("'b' in ('a', 'b')"));
+ }
+
+ public void testInOnTupleDoesNotContain() throws Exception {
+ assertEquals(Boolean.FALSE, eval("'c' in ('a', 'b')"));
+ }
+
+ public void testInOnDictContains() throws Exception {
+ assertEquals(Boolean.TRUE, eval("'b' in {'a' : 1, 'b' : 2}"));
+ }
+
+ public void testInOnDictDoesNotContainKey() throws Exception {
+ assertEquals(Boolean.FALSE, eval("'c' in {'a' : 1, 'b' : 2}"));
+ }
+
+ public void testInOnDictDoesNotContainVal() throws Exception {
+ assertEquals(Boolean.FALSE, eval("1 in {'a' : 1, 'b' : 2}"));
+ }
+
+ public void testInOnStringContains() throws Exception {
+ assertEquals(Boolean.TRUE, eval("'b' in 'abc'"));
+ }
+
+ public void testInOnStringDoesNotContain() throws Exception {
+ assertEquals(Boolean.FALSE, eval("'d' in 'abc'"));
+ }
+
+ public void testInOnStringLeftNotString() throws Exception {
+ checkEvalError("1 in '123'",
+ "in operator only works on strings if the left operand is also a string");
+ }
+
+ public void testInFailsOnNonIterable() throws Exception {
+ checkEvalError("'a' in 1",
+ "in operator only works on lists, tuples, dictionaries and strings");
+ }
+
+ public void testInCompositeForPrecedence() throws Exception {
+ assertEquals(0, eval("not 'a' in ['a'] or 0"));
+ }
+
+ private Object createObjWithStr() {
+ return new Object() {
+ @Override
+ public String toString() {
+ return "str marker";
+ }
+ };
+ }
+
+ public void testPercOnObject() throws Exception {
+ env.update("obj", createObjWithStr());
+ assertEquals("str marker", eval("'%s' % obj", env));
+ }
+
+ public void testPercOnObjectList() throws Exception {
+ env.update("obj", createObjWithStr());
+ assertEquals("str marker str marker", eval("'%s %s' % (obj, obj)", env));
+ }
+
+ public void testPercOnObjectInvalidFormat() throws Exception {
+ env.update("obj", createObjWithStr());
+ checkEvalError("'%d' % obj", env, "invalid arguments for format string");
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testDictKeys() throws Exception {
+ exec("v = {'a': 1}.keys() + ['b', 'c']", env);
+ assertThat((Iterable<Object>) env.lookup("v")).containsExactly("a", "b", "c").inOrder();
+ }
+
+ public void testDictKeysTooManyArgs() throws Exception {
+ checkEvalError("{'a': 1}.keys('abc')", env, "Invalid number of arguments (expected 0)");
+ checkEvalError("{'a': 1}.keys(arg='abc')", env, "Invalid number of arguments (expected 0)");
+ }
+
+ protected void checkEvalError(String input, String msg) throws Exception {
+ checkEvalError(input, env, msg);
+ }
+
+ protected void checkEvalError(String input, Environment env, String msg) throws Exception {
+ try {
+ eval(input, env);
+ fail();
+ } catch (EvalException e) {
+ assertEquals(msg, e.getMessage());
+ }
+ }
+
+ protected void checkEvalError(Statement input, Environment env, String msg) throws Exception {
+ checkEvalError(ImmutableList.of(input), env, msg);
+ }
+
+ protected void checkEvalError(List<Statement> input, Environment env, String msg)
+ throws Exception {
+ try {
+ exec(input, env);
+ fail();
+ } catch (EvalException e) {
+ assertEquals(msg, e.getMessage());
+ }
+ }
+}