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());
+    }
+  }
+}