blob: 73c5a5af7b7cef4fdb3d205f246fdcfc8b536835 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.starlark.java.eval;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.starlark.java.syntax.FileOptions;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.SyntaxError;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Test of evaluation behavior. (Implicitly uses lexer + parser.) */
// TODO(adonovan): separate tests of parser, resolver, Starlark core evaluator,
// and BUILD and .bzl features.
@RunWith(JUnit4.class)
public final class EvaluationTest {
private final EvaluationTestCase ev = new EvaluationTestCase();
@Test
public void testExecutionStopsAtFirstError() throws Exception {
List<String> printEvents = new ArrayList<>();
ParserInput input = ParserInput.fromLines("print('hello'); x = 1//0; print('goodbye')");
InterruptFunction interrupt = new InterruptFunction();
assertThrows(EvalException.class, () -> execWithInterrupt(input, interrupt, printEvents));
// Only expect hello, should have been an error before goodbye.
assertThat(printEvents.toString()).isEqualTo("[hello]");
}
@Test
public void testExecutionNotStartedOnInterrupt() throws Exception {
ParserInput input = ParserInput.fromLines("print('hello')");
List<String> printEvents = new ArrayList<>();
Thread.currentThread().interrupt();
InterruptFunction interrupt = new InterruptFunction();
assertThrows(
InterruptedException.class, () -> execWithInterrupt(input, interrupt, printEvents));
// Execution didn't reach print.
assertThat(printEvents).isEmpty();
}
@Test
public void testForLoopAbortedOnInterrupt() throws Exception {
ParserInput input =
ParserInput.fromLines(
"def f():", //
" for i in range(100):",
" interrupt(i == 5)",
"f()");
InterruptFunction interrupt = new InterruptFunction();
assertThrows(
InterruptedException.class, () -> execWithInterrupt(input, interrupt, new ArrayList<>()));
assertThat(interrupt.callCount).isEqualTo(6);
}
@Test
public void testForComprehensionAbortedOnInterrupt() throws Exception {
ParserInput input = ParserInput.fromLines("[interrupt(i == 5) for i in range(100)]");
InterruptFunction interrupt = new InterruptFunction();
assertThrows(
InterruptedException.class, () -> execWithInterrupt(input, interrupt, new ArrayList<>()));
assertThat(interrupt.callCount).isEqualTo(6);
}
@Test
public void testFunctionCallsNotStartedOnInterrupt() throws Exception {
ParserInput input =
ParserInput.fromLines("interrupt(False); interrupt(True); interrupt(False);");
InterruptFunction interrupt = new InterruptFunction();
assertThrows(
InterruptedException.class, () -> execWithInterrupt(input, interrupt, new ArrayList<>()));
// Third call shouldn't happen.
assertThat(interrupt.callCount).isEqualTo(2);
}
private static class InterruptFunction implements StarlarkCallable {
private int callCount = 0;
@Override
public String getName() {
return "interrupt";
}
@Override
public Object fastcall(StarlarkThread thread, Object[] positional, Object[] named) {
callCount++;
if (positional.length > 0 && Starlark.truth(positional[0])) {
Thread.currentThread().interrupt();
}
return Starlark.NONE;
}
}
// Executes input, with the specified 'interrupt' predeclared built-in, gather print events in
// printEvents.
private static void execWithInterrupt(
ParserInput input, InterruptFunction interrupt, List<String> printEvents) throws Exception {
Module module =
Module.withPredeclared(StarlarkSemantics.DEFAULT, ImmutableMap.of("interrupt", interrupt));
try (Mutability mu = Mutability.create("test")) {
StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
thread.setPrintHandler((_thread, msg) -> printEvents.add(msg));
Starlark.execFile(input, FileOptions.DEFAULT, module, thread);
} finally {
// Reset interrupt bit in case the test failed to do so.
Thread.interrupted();
}
}
@Test
public void testExecutionSteps() throws Exception {
Mutability mu = Mutability.create("test");
StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
ParserInput input = ParserInput.fromLines("squares = [x*x for x in range(n)]");
class C {
long run(int n) throws SyntaxError.Exception, EvalException, InterruptedException {
Module module =
Module.withPredeclared(
StarlarkSemantics.DEFAULT, ImmutableMap.of("n", StarlarkInt.of(n)));
long steps0 = thread.getExecutedSteps();
Starlark.execFile(input, FileOptions.DEFAULT, module, thread);
return thread.getExecutedSteps() - steps0;
}
}
// A thread records the number of computation steps.
long steps1000 = new C().run(1000);
long steps10000 = new C().run(10000);
double ratio = (double) steps10000 / (double) steps1000;
if (ratio < 9.9 || ratio > 10.1) {
throw new AssertionError(
String.format(
"computation steps did not increase linearly: f(1000)=%d, f(10000)=%d, ratio=%g, want"
+ " ~10",
steps1000, steps10000, ratio));
}
// Exceeding the limit causes cancellation.
thread.setMaxExecutionSteps(1000);
EvalException ex = assertThrows(EvalException.class, () -> new C().run(1000));
assertThat(ex).hasMessageThat().contains("Starlark computation cancelled: too many steps");
}
@Test
public void testExprs() throws Exception {
ev.new Scenario()
.testExpression("'%sx' % 'foo' + 'bar1'", "fooxbar1")
.testExpression("('%sx' % 'foo') + 'bar2'", "fooxbar2")
.testExpression("'%sx' % ('foo' + 'bar3')", "foobar3x")
.testExpression("123 + 456", StarlarkInt.of(579))
.testExpression("456 - 123", StarlarkInt.of(333))
.testExpression("8 % 3", StarlarkInt.of(2))
.testIfErrorContains("unsupported binary operation: int % string", "3 % 'foo'")
.testExpression("-5", StarlarkInt.of(-5))
.testIfErrorContains("unsupported unary operation: -string", "-'foo'");
}
@Test
public void testListExprs() throws Exception {
ev.new Scenario()
.testExactOrder("[1, 2, 3]", StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))
.testExactOrder("(1, 2, 3)", StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3));
}
@Test
public void testStringFormatMultipleArgs() throws Exception {
ev.new Scenario().testExpression("'%sY%s' % ('X', 'Z')", "XYZ");
}
@Test
public void testConditionalExpressions() throws Exception {
ev.new Scenario()
.testExpression("1 if True else 2", StarlarkInt.of(1))
.testExpression("1 if False else 2", StarlarkInt.of(2))
.testExpression("1 + 2 if 3 + 4 else 5 + 6", StarlarkInt.of(3));
}
@Test
public void testListComparison() throws Exception {
ev.new Scenario()
.testExpression("[] < [1]", true)
.testExpression("[1] < [1, 1]", true)
.testExpression("[1, 1] < [1, 2]", true)
.testExpression("[1, 2] < [1, 2, 3]", true)
.testExpression("[1, 2, 3] <= [1, 2, 3]", true)
.testExpression("['a', 'b'] > ['a']", true)
.testExpression("['a', 'b'] >= ['a']", true)
.testExpression("['a', 'b'] < ['a']", false)
.testExpression("['a', 'b'] <= ['a']", false)
.testExpression("('a', 'b') > ('a', 'b')", false)
.testExpression("('a', 'b') >= ('a', 'b')", true)
.testExpression("('a', 'b') < ('a', 'b')", false)
.testExpression("('a', 'b') <= ('a', 'b')", true)
.testExpression("[[1, 1]] > [[1, 1], []]", false)
.testExpression("[[1, 1]] < [[1, 1], []]", true);
}
@Test
public void testSumFunction() throws Exception {
StarlarkCallable sum =
new StarlarkCallable() {
@Override
public String getName() {
return "sum";
}
@Override
public StarlarkInt fastcall(StarlarkThread thread, Object[] positional, Object[] named)
throws EvalException {
StarlarkInt sum = StarlarkInt.of(0);
for (Object arg : positional) {
sum = StarlarkInt.add(sum, (StarlarkInt) arg);
}
return sum;
}
};
ev.new Scenario()
.update(sum.getName(), sum)
.testExpression("sum(1, 2, 3, 4, 5, 6)", StarlarkInt.of(21))
.testExpression("sum", sum)
.testExpression("sum(a=1, b=2)", StarlarkInt.of(0));
}
@Test
public void testNotCallInt() throws Exception {
ev.new Scenario()
.setUp("sum = 123456")
.testLookup("sum", StarlarkInt.of(123456))
.testIfExactError("'int' object is not callable", "sum(1, 2, 3, 4, 5, 6)")
.testExpression("sum", StarlarkInt.of(123456));
}
@Test
public void testComplexFunctionCall() throws Exception {
ev.new Scenario()
.setUp("functions = [min, max]", "l = [1,2]")
.testEval("(functions[0](l), functions[1](l))", "(1, 2)");
}
@Test
public void testKeywordArgs() throws Exception {
// This function returns the map of keyword arguments passed to it.
StarlarkCallable kwargs =
new StarlarkCallable() {
@Override
public String getName() {
return "kwargs";
}
@Override
public Object call(
StarlarkThread thread,
Tuple<Object> args,
Dict<String, Object> kwargs) {
return kwargs;
}
};
ev.new Scenario()
.update(kwargs.getName(), kwargs)
.testEval(
"kwargs(foo=1, bar='bar', wiz=[1,2,3]).items()",
"[('foo', 1), ('bar', 'bar'), ('wiz', [1, 2, 3])]")
.testEval(
"kwargs(wiz=[1,2,3], bar='bar', foo=1).items()",
"[('wiz', [1, 2, 3]), ('bar', 'bar'), ('foo', 1)]");
}
@Test
public void testModulo() throws Exception {
ev.new Scenario()
.testExpression("6 % 2", StarlarkInt.of(0))
.testExpression("6 % 4", StarlarkInt.of(2))
.testExpression("3 % 6", StarlarkInt.of(3))
.testExpression("7 % -4", StarlarkInt.of(-1))
.testExpression("-7 % 4", StarlarkInt.of(1))
.testExpression("-7 % -4", StarlarkInt.of(-3))
.testIfExactError("integer modulo by zero", "5 % 0");
}
@Test
public void testMult() throws Exception {
ev.new Scenario()
.testExpression("6 * 7", StarlarkInt.of(42))
.testExpression("3 * 'ab'", "ababab")
.testExpression("0 * 'ab'", "")
.testExpression("'1' + '0' * 5", "100000")
.testExpression("'ab' * -4", "")
.testExpression("-1 * ''", "");
}
@Test
public void testFloorDivision() throws Exception {
ev.new Scenario()
.testExpression("6 // 2", StarlarkInt.of(3))
.testExpression("6 // 4", StarlarkInt.of(1))
.testExpression("3 // 6", StarlarkInt.of(0))
.testExpression("7 // -2", StarlarkInt.of(-4))
.testExpression("-7 // 2", StarlarkInt.of(-4))
.testExpression("-7 // -2", StarlarkInt.of(3))
.testExpression("2147483647 // 2", StarlarkInt.of(1073741823))
.testIfErrorContains("unsupported binary operation: string // int", "'str' // 2")
.testIfExactError("integer division by zero", "5 // 0");
}
@Test
public void testArithmeticDoesNotOverflow() throws Exception {
ev.new Scenario()
.testEval("2000000000 + 2000000000", "1000000000 + 1000000000 + 1000000000 + 1000000000")
.testExpression("1234567890 * 987654321", StarlarkInt.of(1219326311126352690L))
.testExpression(
"1234567890 * 987654321 * 987654321",
StarlarkInt.multiply(StarlarkInt.of(1219326311126352690L), StarlarkInt.of(987654321)))
.testEval("- 2000000000 - 2000000000", "-1000000000 - 1000000000 - 1000000000 - 1000000000")
// literal 2147483648 is not allowed, so we compute it
.setUp("minint = - 2147483647 - 1")
.testEval("-minint", "2147483647+1");
}
@Test
public void testOperatorPrecedence() throws Exception {
ev.new Scenario()
.testExpression("2 + 3 * 4", StarlarkInt.of(14))
.testExpression("2 + 3 // 4", StarlarkInt.of(2))
.testExpression("2 * 3 + 4 // -2", StarlarkInt.of(4));
}
@Test
public void testConcatStrings() throws Exception {
ev.new Scenario().testExpression("'foo' + 'bar'", "foobar");
}
@Test
public void testConcatLists() throws Exception {
ev.new Scenario()
.testExactOrder(
"[1,2] + [3,4]",
StarlarkInt.of(1),
StarlarkInt.of(2),
StarlarkInt.of(3),
StarlarkInt.of(4))
.testExactOrder("(1,2)", StarlarkInt.of(1), StarlarkInt.of(2))
.testExactOrder(
"(1,2) + (3,4)",
StarlarkInt.of(1),
StarlarkInt.of(2),
StarlarkInt.of(3),
StarlarkInt.of(4));
// TODO(fwe): cannot be handled by current testing suite
// list
Object x = ev.eval("[1,2] + [3,4]");
assertThat((Iterable<?>) x)
.containsExactly(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4))
.inOrder();
assertThat(x).isInstanceOf(StarlarkList.class);
assertThat(Starlark.isImmutable(x)).isFalse();
// tuple
x = ev.eval("(1,2) + (3,4)");
assertThat((Iterable<?>) x)
.containsExactly(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4))
.inOrder();
assertThat(x).isInstanceOf(Tuple.class);
assertThat(x)
.isEqualTo(
Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4)));
assertThat(Starlark.isImmutable(x)).isTrue();
ev.checkEvalError("unsupported binary operation: tuple + list", "(1,2) + [3,4]");
}
@Test
public void testListComprehensionDefinitionOrder() throws Exception {
// This exercises the .bzl file behavior. This is a dynamic error.
// (The error message for BUILD files is slightly different (no "local")
// because it doesn't record the scope in the syntax tree.)
ev.new Scenario()
.testIfErrorContains(
"local variable 'y' is referenced before assignment", //
"[x for x in (1, 2) if y for y in (3, 4)]");
// This is the corresponding test for BUILD files.
EvalException ex =
assertThrows(
EvalException.class, () -> execBUILD("[x for x in (1, 2) if y for y in (3, 4)]"));
assertThat(ex).hasMessageThat().isEqualTo("variable 'y' is referenced before assignment");
}
private static void execBUILD(String... lines)
throws SyntaxError.Exception, EvalException, InterruptedException {
ParserInput input = ParserInput.fromLines(lines);
FileOptions options = FileOptions.builder().recordScope(false).build();
try (Mutability mu = Mutability.create("test")) {
StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
Starlark.execFile(input, options, Module.create(), thread);
}
}
@Test
public void testTupleDestructuring() throws Exception {
ev.new Scenario()
.setUp("a, b = 1, 2")
.testLookup("a", StarlarkInt.of(1))
.testLookup("b", StarlarkInt.of(2))
.setUp("c, d = {'key1':2, 'key2':3}")
.testLookup("c", "key1")
.testLookup("d", "key2");
}
@Test
public void testSingleTuple() throws Exception {
ev.new Scenario().setUp("(a,) = [1]").testLookup("a", StarlarkInt.of(1));
}
@Test
public void testHeterogeneousDict() throws Exception {
ev.new Scenario()
.setUp("d = {'str': 1, 2: 3}", "a = d['str']", "b = d[2]")
.testLookup("a", StarlarkInt.of(1))
.testLookup("b", StarlarkInt.of(3));
}
@Test
public void testAccessDictWithATupleKey() throws Exception {
ev.new Scenario().setUp("x = {(1, 2): 3}[1, 2]").testLookup("x", StarlarkInt.of(3));
}
@Test
public void testDictWithDuplicatedKey() throws Exception {
ev.new Scenario()
.testIfErrorContains(
"dictionary expression has duplicate key: \"str\"", "{'str': 1, 'x': 2, 'str': 3}");
}
@Test
public void testRecursiveTupleDestructuring() throws Exception {
ev.new Scenario()
.setUp("((a, b), (c, d)) = [(1, 2), (3, 4)]")
.testLookup("a", StarlarkInt.of(1))
.testLookup("b", StarlarkInt.of(2))
.testLookup("c", StarlarkInt.of(3))
.testLookup("d", StarlarkInt.of(4));
}
@Test
public void testListComprehensionAtTopLevel() throws Exception {
// It is allowed to have a loop variable with the same name as a global variable.
ev.new Scenario()
.update("x", StarlarkInt.of(42))
.setUp("y = [x + 1 for x in [1,2,3]]")
.testExactOrder("y", StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4));
}
@Test
public void testDictComprehensions() throws Exception {
ev.new Scenario()
.testExpression("{a : a for a in []}", Collections.emptyMap())
.testExpression(
"{b : b for b in [1, 2]}",
ImmutableMap.of(
StarlarkInt.of(1), StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(2)))
.testExpression(
"{c : 'v_' + c for c in ['a', 'b']}", ImmutableMap.of("a", "v_a", "b", "v_b"))
.testExpression(
"{'k_' + d : d for d in ['a', 'b']}", ImmutableMap.of("k_a", "a", "k_b", "b"))
.testExpression(
"{'k_' + e : 'v_' + e for e in ['a', 'b']}",
ImmutableMap.of("k_a", "v_a", "k_b", "v_b"))
.testExpression(
"{x+y : x*y for x, y in [[2, 3]]}",
ImmutableMap.of(StarlarkInt.of(5), StarlarkInt.of(6)));
}
@Test
public void testDictComprehensionOnNonIterable() throws Exception {
ev.new Scenario().testIfExactError("type 'int' is not iterable", "{k : k for k in 3}");
}
@Test
public void testDictComprehension_manyClauses() throws Exception {
ev.new Scenario()
.testExpression(
"{x : x * y for x in range(1, 10) if x % 2 == 0 for y in range(1, 10) if y == x}",
ImmutableMap.of(
StarlarkInt.of(2),
StarlarkInt.of(4),
StarlarkInt.of(4),
StarlarkInt.of(16),
StarlarkInt.of(6),
StarlarkInt.of(36),
StarlarkInt.of(8),
StarlarkInt.of(64)));
}
@Test
public void testDictComprehensions_multipleKey() throws Exception {
ev.new Scenario()
.testExpression(
"{x : x for x in [1, 2, 1]}",
ImmutableMap.of(
StarlarkInt.of(1), StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(2)))
.testExpression(
"{y : y for y in ['ab', 'c', 'a' + 'b']}", ImmutableMap.of("ab", "ab", "c", "c"));
}
@Test
public void testListConcatenation() throws Exception {
ev.new Scenario()
.testEval("[1, 2] + [3, 4]", "[1, 2, 3, 4]")
.testEval("(1, 2) + (3, 4)", "(1, 2, 3, 4)")
.testIfExactError("unsupported binary operation: list + tuple", "[1, 2] + (3, 4)")
.testIfExactError("unsupported binary operation: tuple + list", "(1, 2) + [3, 4]");
}
@Test
public void testListMultiply() throws Exception {
ev.new Scenario()
.testEval("[1, 2, 3] * 1", "[1, 2, 3]")
.testEval("[1, 2] * 2", "[1, 2, 1, 2]")
.testEval("[1, 2] * 3", "[1, 2, 1, 2, 1, 2]")
.testEval("[1, 2] * 4", "[1, 2, 1, 2, 1, 2, 1, 2]")
.testEval("[8] * 5", "[8, 8, 8, 8, 8]")
.testEval("[ ] * 10", "[]")
.testEval("[1, 2] * 0", "[]")
.testEval("[1, 2] * -4", "[]")
.testEval("2 * [1, 2]", "[1, 2, 1, 2]")
.testEval("10 * []", "[]")
.testEval("0 * [1, 2]", "[]")
.testEval("-4 * [1, 2]", "[]");
}
@Test
public void testTupleMultiply() throws Exception {
ev.new Scenario()
.testEval("(1, 2, 3) * 1", "(1, 2, 3)")
.testEval("(1, 2) * 2", "(1, 2, 1, 2)")
.testEval("(1, 2) * 3", "(1, 2, 1, 2, 1, 2)")
.testEval("(1, 2) * 4", "(1, 2, 1, 2, 1, 2, 1, 2)")
.testEval("(8,) * 5", "(8, 8, 8, 8, 8)")
.testEval("( ) * 10", "()")
.testEval("(1, 2) * 0", "()")
.testEval("(1, 2) * -4", "()")
.testEval("2 * (1, 2)", "(1, 2, 1, 2)")
.testEval("10 * ()", "()")
.testEval("0 * (1, 2)", "()")
.testEval("-4 * (1, 2)", "()");
}
@Test
public void testListComprehensionFailsOnNonSequence() throws Exception {
ev.new Scenario().testIfErrorContains("type 'int' is not iterable", "[x + 1 for x in 123]");
}
@Test
public void testListComprehensionOnStringIsForbidden() throws Exception {
ev.new Scenario().testIfErrorContains("type 'string' is not iterable", "[x for x in 'abc']");
}
@Test
public void testInvalidAssignment() throws Exception {
ev.new Scenario().testIfErrorContains("cannot assign to 'x + 1'", "x + 1 = 2");
}
@Test
public void testListComprehensionOnDictionary() throws Exception {
ev.new Scenario().testExactOrder("['var_' + n for n in {'a':1,'b':2}]", "var_a", "var_b");
}
@Test
public void testListComprehensionOnDictionaryCompositeExpression() throws Exception {
ev.new Scenario()
.setUp("d = {1:'a',2:'b'}", "l = [d[x] for x in d]")
.testLookup("l", StarlarkList.of(null, "a", "b"));
}
@Test
public void testListComprehensionUpdate() throws Exception {
ev.new Scenario()
.setUp("xs = [1, 2, 3]")
.testIfErrorContains(
"list value is temporarily immutable due to active for-loop iteration",
"[xs.append(4) for x in xs]");
}
@Test
public void testNestedListComprehensionUpdate() throws Exception {
ev.new Scenario()
.setUp("xs = [1, 2, 3]")
.testIfErrorContains(
"list value is temporarily immutable due to active for-loop iteration",
"[xs.append(4) for x in xs for y in xs]");
}
@Test
public void testListComprehensionUpdateInClause() throws Exception {
ev.new Scenario()
.setUp("xs = [1, 2, 3]")
.testIfErrorContains(
"list value is temporarily immutable due to active for-loop iteration",
// Use short-circuiting to produce valid output in the event
// the exception is not raised.
"[y for x in xs for y in (xs.append(4) or xs)]");
}
@Test
public void testDictComprehensionUpdate() throws Exception {
ev.new Scenario()
.setUp("xs = {1:1, 2:2, 3:3}")
.testIfErrorContains(
"dict value is temporarily immutable due to active for-loop iteration",
"[xs.popitem() for x in xs]");
}
@Test
public void testListComprehensionScope() throws Exception {
// Test list comprehension creates a scope, so outer variables kept unchanged
ev.new Scenario()
.setUp("x = 1", "l = [x * 3 for x in [2]]", "y = x")
.testEval("y", "1")
.testEval("l", "[6]");
}
@Test
public void testInOperator() throws Exception {
ev.new Scenario()
.testExpression("'b' in ['a', 'b']", Boolean.TRUE)
.testExpression("'c' in ['a', 'b']", Boolean.FALSE)
.testExpression("'b' in ('a', 'b')", Boolean.TRUE)
.testExpression("'c' in ('a', 'b')", Boolean.FALSE)
.testExpression("'b' in {'a' : 1, 'b' : 2}", Boolean.TRUE)
.testExpression("'c' in {'a' : 1, 'b' : 2}", Boolean.FALSE)
.testExpression("1 in {'a' : 1, 'b' : 2}", Boolean.FALSE)
.testExpression("'b' in 'abc'", Boolean.TRUE)
.testExpression("'d' in 'abc'", Boolean.FALSE);
}
@Test
public void testNotInOperator() throws Exception {
ev.new Scenario()
.testExpression("'b' not in ['a', 'b']", Boolean.FALSE)
.testExpression("'c' not in ['a', 'b']", Boolean.TRUE)
.testExpression("'b' not in ('a', 'b')", Boolean.FALSE)
.testExpression("'c' not in ('a', 'b')", Boolean.TRUE)
.testExpression("'b' not in {'a' : 1, 'b' : 2}", Boolean.FALSE)
.testExpression("'c' not in {'a' : 1, 'b' : 2}", Boolean.TRUE)
.testExpression("1 not in {'a' : 1, 'b' : 2}", Boolean.TRUE)
.testExpression("'b' not in 'abc'", Boolean.FALSE)
.testExpression("'d' not in 'abc'", Boolean.TRUE);
}
@Test
public void testInFail() throws Exception {
ev.new Scenario()
.testIfErrorContains(
"'in <string>' requires string as left operand, not 'int'", "1 in '123'")
.testIfErrorContains("unsupported binary operation: string in int", "'a' in 1");
}
@Test
public void testInCompositeForPrecedence() throws Exception {
ev.new Scenario().testExpression("not 'a' in ['a'] or 0", StarlarkInt.of(0));
}
private static StarlarkValue createObjWithStr() {
return new StarlarkValue() {
@Override
public void repr(Printer printer) {
printer.append("<str marker>");
}
};
}
@Test
public void testPercentOnObjWithStr() throws Exception {
ev.new Scenario()
.update("obj", createObjWithStr())
.testExpression("'%s' % obj", "<str marker>");
}
private static class Dummy implements StarlarkValue {}
@Test
public void testStringRepresentationsOfArbitraryObjects() throws Exception {
String dummy = "<unknown object net.starlark.java.eval.EvaluationTest$Dummy>";
ev.new Scenario()
.update("dummy", new Dummy())
.testExpression("str(dummy)", dummy)
.testExpression("repr(dummy)", dummy)
.testExpression("'{}'.format(dummy)", dummy)
.testExpression("'%s' % dummy", dummy)
.testExpression("'%r' % dummy", dummy);
}
@Test
public void testPercentOnTupleOfDummyValues() throws Exception {
ev.new Scenario()
.update("obj", createObjWithStr())
.testExpression("'%s %s' % (obj, obj)", "<str marker> <str marker>");
ev.new Scenario()
.update("unknown", new Dummy())
.testExpression(
"'%s %s' % (unknown, unknown)",
"<unknown object net.starlark.java.eval.EvaluationTest$Dummy> <unknown"
+ " object net.starlark.java.eval.EvaluationTest$Dummy>");
}
@Test
public void testPercOnObjectInvalidFormat() throws Exception {
ev.new Scenario()
.update("obj", createObjWithStr())
.testIfExactError("invalid argument <str marker> for format pattern %d", "'%d' % obj");
}
@Test
public void testDictKeys() throws Exception {
ev.new Scenario().testExactOrder("{'a': 1}.keys() + ['b', 'c']", "a", "b", "c");
}
@Test
public void testDictKeysTooManyArgs() throws Exception {
ev.new Scenario()
.testIfExactError("keys() got unexpected positional argument", "{'a': 1}.keys('abc')");
}
@Test
public void testDictKeysTooManyKeyArgs() throws Exception {
ev.new Scenario()
.testIfExactError(
"keys() got unexpected keyword argument 'arg'", "{'a': 1}.keys(arg='abc')");
}
@Test
public void testDictKeysDuplicateKeyArgs() throws Exception {
// f(a=1, a=2) is caught statically by the resolver.
ev.new Scenario()
.testIfExactError(
"int() got multiple values for argument 'base'", "int('1', base=10, **dict(base=16))");
}
@Test
public void testArgBothPosKey() throws Exception {
ev.new Scenario()
.testIfErrorContains(
"int() got multiple values for argument 'base'", "int('2', 3, base=3)");
}
@Test
public void testStaticNameResolution() throws Exception {
ev.new Scenario().testIfErrorContains("name 'foo' is not defined", "[foo for x in []]");
}
@Test
public void testExec() throws Exception {
ParserInput input =
ParserInput.fromLines(
"# a file in the build language",
"",
"x = [1, 2, 'foo', 4] + [1, 2, \"%s%d\" % ('foo', 1)]");
Module module = Module.create();
try (Mutability mu = Mutability.create("test")) {
StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
Starlark.execFile(input, FileOptions.DEFAULT, module, thread);
}
assertThat(module.getGlobal("x"))
.isEqualTo(
StarlarkList.of(
/*mutability=*/ null,
StarlarkInt.of(1),
StarlarkInt.of(2),
"foo",
StarlarkInt.of(4),
StarlarkInt.of(1),
StarlarkInt.of(2),
"foo1"));
}
}