blob: b764413ce4f13e718e83b1d05a46bf1b5a3a6b16 [file] [log] [blame]
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Test of evaluation behavior. (Implicitly uses lexer + parser.)
*/
@RunWith(JUnit4.class)
public class EvaluationTest extends EvaluationTestCase {
@Override
public EvaluationContext newEvaluationContext() {
return EvaluationContext.newBuildContext(getEventHandler(),
new PackageFactory(TestRuleClassProvider.getRuleClassProvider()).getEnvironment());
}
@Test
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"));
checkEvalErrorContains("unsupported operand type(s) for %: 'int' and 'string'", "3 % 'foo'");
}
@SuppressWarnings("unchecked")
@Test
public void testListExprs() throws Exception {
assertThat((Iterable<Object>) eval("[1, 2, 3]")).containsExactly(1, 2, 3).inOrder();
assertThat((Iterable<Object>) eval("(1, 2, 3)")).containsExactly(1, 2, 3).inOrder();
}
@Test
public void testStringFormatMultipleArgs() throws Exception {
assertEquals("XYZ", eval("'%sY%s' % ('X', 'Z')"));
}
@Test
public void testAndOr() throws Exception {
assertEquals(8, eval("8 or 9"));
assertEquals(9, eval("0 or 9"));
assertEquals(9, eval("8 and 9"));
assertEquals(0, eval("0 and 9"));
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(Environment.NONE, eval("None and 1"));
assertEquals(9, eval("\"\" or 9"));
assertEquals("abc", eval("\"abc\" or 9"));
if (isSkylark()) {
checkEvalError("ERROR 1:6: name 'foo' is not defined", "8 or foo");
checkEvalError("ERROR 1:7: name 'foo' is not defined", "0 and foo");
} else {
assertEquals(8, eval("8 or foo")); // check that 'foo' is not evaluated
assertEquals(0, eval("0 and foo")); // check that 'foo' is not evaluated
}
}
@Test
public void testNot() throws Exception {
assertEquals(false, eval("not 1"));
assertEquals(true, eval("not ''"));
}
@Test
public void testNotWithLogicOperators() throws Exception {
assertEquals(true, eval("not (0 and 0)"));
assertEquals(false, eval("not (1 or 0)"));
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"));
}
@Test
public void testNotWithArithmeticOperators() throws Exception {
assertEquals(true, eval("not 0 + 0"));
assertEquals(false, eval("not 2 - 1"));
}
@Test
public void testNotWithCollections() throws Exception {
assertEquals(true, eval("not []"));
assertEquals(false, eval("not {'a' : 1}"));
}
@Test
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("None == None"));
assertEquals(true, eval("[1, 2] == [1, 2]"));
assertEquals(false, eval("[1, 2] == [2, 1]"));
}
@Test
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]"));
}
@Test
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"));
}
@Test
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'"));
}
@Test
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'"));
}
@Test
public void testConditionalExpressions() throws Exception {
assertEquals(1, eval("1 if True else 2"));
assertEquals(2, eval("1 if False else 2"));
assertEquals(3, eval("1 + 2 if 3 + 4 else 5 + 6"));
setFailFast(false);
parseExpression("1 if 2");
assertContainsEvent(
"missing else clause in conditional expression or semicolon before if");
}
@Test
public void testListComparison() throws Exception {
assertThat(eval("[] < [1]")).isEqualTo(true);
assertThat(eval("[1] < [1, 1]")).isEqualTo(true);
assertThat(eval("[1, 1] < [1, 2]")).isEqualTo(true);
assertThat(eval("[1, 2] < [1, 2, 3]")).isEqualTo(true);
assertThat(eval("[1, 2, 3] <= [1, 2, 3]")).isEqualTo(true);
assertThat(eval("['a', 'b'] > ['a']")).isEqualTo(true);
assertThat(eval("['a', 'b'] >= ['a']")).isEqualTo(true);
assertThat(eval("['a', 'b'] < ['a']")).isEqualTo(false);
assertThat(eval("['a', 'b'] <= ['a']")).isEqualTo(false);
assertThat(eval("('a', 'b') > ('a', 'b')")).isEqualTo(false);
assertThat(eval("('a', 'b') >= ('a', 'b')")).isEqualTo(true);
assertThat(eval("('a', 'b') < ('a', 'b')")).isEqualTo(false);
assertThat(eval("('a', 'b') <= ('a', 'b')")).isEqualTo(true);
assertThat(eval("[[1, 1]] > [[1, 1], []]")).isEqualTo(false);
assertThat(eval("[[1, 1]] < [[1, 1], []]")).isEqualTo(true);
checkEvalError("Cannot compare int with string", "[1] < ['a']");
checkEvalError("Cannot compare list with int", "[1] < 1");
}
@Test
public void testCompareStringInt() throws Exception {
checkEvalError("Cannot compare string with int", "'a' >= 1");
}
@Test
public void testSumFunction() throws Exception {
BaseFunction sum = new BaseFunction("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;
}
};
update(sum.getName(), sum);
assertEquals(21, eval("sum(1, 2, 3, 4, 5, 6)"));
assertEquals(sum, eval("sum"));
assertEquals(0, eval("sum(a=1, b=2)"));
}
@Test
public void testNotCallInt() throws Exception {
eval("sum = 123456");
assertEquals(123456, lookup("sum"));
checkEvalError("'int' object is not callable", "sum(1, 2, 3, 4, 5, 6)");
assertEquals(123456, eval("sum"));
}
@Test
public void testKeywordArgs() throws Exception {
// This function returns the map of keyword arguments passed to it.
BaseFunction kwargs = new BaseFunction("kwargs") {
@Override
public Object call(List<Object> args,
final Map<String, Object> kwargs,
FuncallExpression ast,
Environment env) {
return kwargs;
}
};
update(kwargs.getName(), kwargs);
assertEquals(eval("[('bar', 'bar'), ('foo', 1), ('wiz', [1, 2, 3])]"),
eval("kwargs(foo=1, bar='bar', wiz=[1,2,3]).items()"));
}
@Test
public void testModulo() throws Exception {
assertThat(eval("6 % 2")).isEqualTo(0);
assertThat(eval("6 % 4")).isEqualTo(2);
assertThat(eval("3 % 6")).isEqualTo(3);
assertThat(eval("7 % -4")).isEqualTo(-1);
assertThat(eval("-7 % 4")).isEqualTo(1);
assertThat(eval("-7 % -4")).isEqualTo(-3);
checkEvalError("integer modulo by zero", "5 % 0");
}
@Test
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"));
}
@Test
public void testDivision() throws Exception {
assertThat(eval("6 / 2")).isEqualTo(3);
assertThat(eval("6 / 4")).isEqualTo(1);
assertThat(eval("3 / 6")).isEqualTo(0);
assertThat(eval("7 / -2")).isEqualTo(-4);
assertThat(eval("-7 / 2")).isEqualTo(-4);
assertThat(eval("-7 / -2")).isEqualTo(3);
assertThat(eval("2147483647 / 2")).isEqualTo(1073741823);
checkEvalError("integer division by zero", "5 / 0");
}
@Test
public void testOperatorPrecedence() throws Exception {
assertThat(eval("2 + 3 * 4")).isEqualTo(14);
assertThat(eval("2 + 3 / 4")).isEqualTo(2);
assertThat(eval("2 * 3 + 4 / -2")).isEqualTo(4);
}
@Test
public void testConcatStrings() throws Exception {
assertEquals("foobar", eval("'foo' + 'bar'"));
}
@SuppressWarnings("unchecked")
@Test
public void testConcatLists() throws Exception {
// list
Object x = eval("[1,2] + [3,4]");
assertThat((Iterable<Object>) x).containsExactly(1, 2, 3, 4).inOrder();
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("can only concatenate List (not \"Tuple\") to List",
"(1,2) + [3,4]"); // list + tuple
}
@SuppressWarnings("unchecked")
@Test
public void testListComprehensions() throws Exception {
assertThat((Iterable<Object>) eval("['foo/%s.java' % x for x in []]")).isEmpty();
assertThat((Iterable<Object>) eval("['foo/%s.java' % y for y in ['bar', 'wiz', 'quux']]"))
.containsExactly("foo/bar.java", "foo/wiz.java", "foo/quux.java").inOrder();
assertThat((Iterable<Object>) eval(
"['%s/%s.java' % (z, t) "
+ "for z in ['foo', 'bar'] "
+ "for t in ['baz', 'wiz', 'quux']]"))
.containsExactly("foo/baz.java", "foo/wiz.java", "foo/quux.java",
"bar/baz.java", "bar/wiz.java", "bar/quux.java").inOrder();
assertThat((Iterable<Object>) eval(
"['%s/%s.java' % (b, b) "
+ "for a in ['foo', 'bar'] "
+ "for b in ['baz', 'wiz', 'quux']]"))
.containsExactly("baz/baz.java", "wiz/wiz.java", "quux/quux.java",
"baz/baz.java", "wiz/wiz.java", "quux/quux.java").inOrder();
assertThat((Iterable<Object>) eval(
"['%s/%s.%s' % (c, d, e) "
+ "for c in ['foo', 'bar'] "
+ "for d in ['baz', 'wiz', 'quux'] "
+ "for e in ['java', 'cc']]"))
.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();
}
@Test
public void testNestedListComprehensions() throws Exception {
assertThat((Iterable<?>) eval(
"li = [[1, 2], [3, 4]]\n"
+ "[j for i in li for j in i]"))
.containsExactly(1, 2, 3, 4).inOrder();
assertThat((Iterable<?>) eval(
"input = [['abc'], ['def', 'ghi']]\n"
+ "['%s %s' % (b, c) for a in input for b in a for c in b]"))
.containsExactly(
"abc a", "abc b", "abc c", "def d", "def e", "def f", "ghi g", "ghi h", "ghi i")
.inOrder();
}
@Test
public void testListComprehensionsMultipleVariables() throws Exception {
assertThat(eval("[x + y for x, y in [(1, 2), (3, 4)]]").toString())
.isEqualTo("[3, 7]");
assertThat(eval("[z + t for (z, t) in [[1, 2], [3, 4]]]").toString())
.isEqualTo("[3, 7]");
}
@Test
public void testListComprehensionsMultipleVariablesFail() throws Exception {
checkEvalError("lvalue has length 3, but rvalue has has length 2",
"[x + y for x, y, z in [(1, 2), (3, 4)]]");
checkEvalError("type 'int' is not a collection",
"[x + y for x, y in (1, 2)]");
}
@Test
public void testListComprehensionsWithFiltering() throws Exception {
eval("range3 = [0, 1, 2]"); // used below
assertThat(eval("[a for a in (4, None, 2, None, 1) if a != None]").toString())
.isEqualTo("[4, 2, 1]");
assertThat(eval("[b+c for b in [0, 1, 2] for c in [0, 1, 2] if b + c > 2]").toString())
.isEqualTo("[3, 3, 4]");
assertThat(eval("[d+e for d in range3 if d % 2 == 1 for e in range3]").toString())
.isEqualTo("[1, 2, 3]");
assertThat(eval(
"[[f,g] for f in [0, 1, 2, 3, 4] if f for g in [5, 6, 7, 8] if f * g % 12 == 0 ]")
.toString()).isEqualTo("[[2, 6], [3, 8], [4, 6]]");
assertThat(eval("[h for h in [4, 2, 0, 1] if h]").toString())
.isEqualTo("[4, 2, 1]");
}
@Test
public void testListComprehensionDefinitionOrder() throws Exception {
checkEvalErrorContains("name 'y' is not defined",
"[x for x in (1, 2) if y for y in (3, 4)]");
}
@Test
public void testTupleDestructuring() throws Exception {
eval("a, b = 1, 2");
assertThat(lookup("a")).isEqualTo(1);
assertThat(lookup("b")).isEqualTo(2);
eval("c, d = {'key1':2, 'key2':3}");
assertThat(lookup("c")).isEqualTo("key1");
assertThat(lookup("d")).isEqualTo("key2");
}
@Test
public void testHeterogeneousDict() throws Exception {
eval("d = {'str': 1, 2: 3}\n"
+ "a = d['str']\n"
+ "b = d[2]");
assertThat(lookup("a")).isEqualTo(1);
assertThat(lookup("b")).isEqualTo(3);
}
@Test
public void testRecursiveTupleDestructuring() throws Exception {
eval("((a, b), (c, d)) = [(1, 2), (3, 4)]");
assertThat(lookup("a")).isEqualTo(1);
assertThat(lookup("b")).isEqualTo(2);
assertThat(lookup("c")).isEqualTo(3);
assertThat(lookup("d")).isEqualTo(4);
}
@SuppressWarnings("unchecked")
@Test
public void testListComprehensionModifiesGlobalEnv() throws Exception {
update("x", 42);
if (isSkylark()) {
checkEvalError("ERROR 1:1: Variable x is read only", "[x + 1 for x in [1,2,3]]");
} else {
assertThat((Iterable<Object>) eval("[x + 1 for x in [1,2,3]]"))
.containsExactly(2, 3, 4).inOrder();
assertEquals(3, lookup("x")); // (x is global)
}
}
@Test
public void testDictComprehensions() throws Exception {
assertEquals(Collections.emptyMap(), eval("{a : a for a in []}"));
assertEquals(ImmutableMap.of(1, 1, 2, 2), eval("{b : b for b in [1, 2]}"));
assertEquals(ImmutableMap.of("a", "v_a", "b", "v_b"),
eval("{c : 'v_' + c for c in ['a', 'b']}"));
assertEquals(ImmutableMap.of("k_a", "a", "k_b", "b"),
eval("{'k_' + d : d for d in ['a', 'b']}"));
assertEquals(ImmutableMap.of("k_a", "v_a", "k_b", "v_b"),
eval("{'k_' + e : 'v_' + e for e in ['a', 'b']}"));
assertEquals(ImmutableMap.of(5, 6), eval("{x+y : x*y for x, y in [[2, 3]]}"));
}
@Test
public void testDictComprehensionOnNonIterable() throws Exception {
checkEvalError(
"type 'int' is not iterable",
"{k : k for k in 3}");
}
@Test
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("{y : y for y in ['ab', 'c', 'a' + 'b']}"));
}
@Test
public void testDictComprehensions_ToString() throws Exception {
assertEquals("{x: x for x in [1, 2]}",
evaluationContext.parseExpression("{x : x for x in [1, 2]}").toString());
assertEquals("{x + 'a': x for x in [1, 2]}",
evaluationContext.parseExpression("{x + 'a' : x for x in [1, 2]}").toString());
}
@Test
public void testListConcatenation() throws Exception {
assertEquals(Arrays.asList(1, 2, 3, 4), eval("[1, 2] + [3, 4]"));
assertEquals(ImmutableList.of(1, 2, 3, 4), eval("(1, 2) + (3, 4)"));
checkEvalError("can only concatenate Tuple (not \"List\") to Tuple",
"[1, 2] + (3, 4)");
checkEvalError("can only concatenate List (not \"Tuple\") to List",
"(1, 2) + [3, 4]");
}
@SuppressWarnings("unchecked")
@Test
public void testSelectorListConcatenation() throws Exception {
SelectorList x = (SelectorList) eval("select({'foo': ['FOO'], 'bar': ['BAR']}) + []");
List<Object> elements = x.getElements();
assertThat(elements.size()).isEqualTo(2);
assertThat(elements.get(0)).isInstanceOf(SelectorValue.class);
assertThat((Iterable) elements.get(1)).isEmpty();
}
@Test
public void testListComprehensionFailsOnNonSequence() throws Exception {
checkEvalErrorContains("type 'int' is not iterable", "[x + 1 for x in 123]");
}
@SuppressWarnings("unchecked")
@Test
public void testListComprehensionOnString() throws Exception {
assertThat((Iterable<Object>) eval("[x for x in 'abc']"))
.containsExactly("a", "b", "c").inOrder();
}
@Test
public void testInvalidAssignment() throws Exception {
update("x", 1);
checkEvalErrorContains("can only assign to variables and tuples, not to 'x + 1'",
"x + 1 = 2");
}
@SuppressWarnings("unchecked")
@Test
public void testListComprehensionOnDictionary() throws Exception {
assertThat((Iterable<Object>) eval("val = ['var_' + n for n in {'a':1,'b':2}] ; val"))
.containsExactly("var_a", "var_b").inOrder();
}
@Test
public void testListComprehensionOnDictionaryCompositeExpression() throws Exception {
eval("d = {1:'a',2:'b'}\n" + "l = [d[x] for x in d]");
assertEquals("[\"a\", \"b\"]", Printer.repr(lookup("l")));
}
@Test
public void testInOperator() throws Exception {
assertEquals(Boolean.TRUE, eval("'b' in ['a', 'b']"));
assertEquals(Boolean.FALSE, eval("'c' in ['a', 'b']"));
assertEquals(Boolean.TRUE, eval("'b' in ('a', 'b')"));
assertEquals(Boolean.FALSE, eval("'c' in ('a', 'b')"));
assertEquals(Boolean.TRUE, eval("'b' in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.FALSE, eval("'c' in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.FALSE, eval("1 in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.TRUE, eval("'b' in 'abc'"));
assertEquals(Boolean.FALSE, eval("'d' in 'abc'"));
}
@Test
public void testNotInOperator() throws Exception {
assertEquals(Boolean.FALSE, eval("'b' not in ['a', 'b']"));
assertEquals(Boolean.TRUE, eval("'c' not in ['a', 'b']"));
assertEquals(Boolean.FALSE, eval("'b' not in ('a', 'b')"));
assertEquals(Boolean.TRUE, eval("'c' not in ('a', 'b')"));
assertEquals(Boolean.FALSE, eval("'b' not in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.TRUE, eval("'c' not in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.TRUE, eval("1 not in {'a' : 1, 'b' : 2}"));
assertEquals(Boolean.FALSE, eval("'b' not in 'abc'"));
assertEquals(Boolean.TRUE, eval("'d' not in 'abc'"));
}
@Test
public void testInFail() throws Exception {
checkEvalError("in operator only works on strings if the left operand is also a string",
"1 in '123'");
checkEvalError("in operator only works on lists, tuples, sets, dicts and strings", "'a' in 1");
}
@Test
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";
}
};
}
@Test
public void testPercOnObject() throws Exception {
update("obj", createObjWithStr());
assertEquals("str marker", eval("'%s' % obj"));
}
@Test
public void testPercOnObjectList() throws Exception {
update("obj", createObjWithStr());
assertEquals("str marker str marker", eval("'%s %s' % (obj, obj)"));
}
@Test
public void testPercOnObjectInvalidFormat() throws Exception {
update("obj", createObjWithStr());
checkEvalError("invalid argument str marker for format pattern %d", "'%d' % obj");
}
@SuppressWarnings("unchecked")
@Test
public void testDictKeys() throws Exception {
assertThat((Iterable<Object>) eval("v = {'a': 1}.keys() + ['b', 'c'] ; v"))
.containsExactly("a", "b", "c").inOrder();
}
@Test
public void testDictKeysTooManyArgs() throws Exception {
checkEvalError("too many (2) positional arguments in call to keys(self: dict)",
"{'a': 1}.keys('abc')");
}
@Test
public void testDictKeysTooManyKeyArgs() throws Exception {
checkEvalError("unexpected keyword 'arg' in call to keys(self: dict)",
"{'a': 1}.keys(arg='abc')");
}
@Test
public void testDictKeysDuplicateKeyArgs() throws Exception {
checkEvalError("duplicate keywords 'arg', 'k' in call to keys",
"{'a': 1}.keys(arg='abc', arg='def', k=1, k=2)");
}
@Test
public void testArgBothPosKey() throws Exception {
checkEvalErrorStartsWith("arguments 'old', 'new' passed both by position and by name "
+ "in call to replace(self: string, ",
"'banana'.replace('a', 'o', 3, old='a', new=4)");
}
}