blob: ea49524a56f0eef5e9471912b3b59bab732c30e5 [file] [log] [blame]
// Copyright 2015 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 com.google.devtools.build.lib.syntax;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for SkylarkNestedSet.
*/
@RunWith(JUnit4.class)
public class SkylarkNestedSetTest extends EvaluationTestCase {
@Test
public void testConstructor() throws Exception {
exec("s = depset(order='default')");
assertThat(lookup("s")).isInstanceOf(SkylarkNestedSet.class);
}
@Test
public void testTuples() throws Exception {
exec(
"s_one = depset([('1', '2'), ('3', '4')])",
"s_two = depset(direct = [('1', '2'), ('3', '4'), ('5', '6')])",
"s_three = depset(transitive = [s_one, s_two])",
"s_four = depset(direct = [('1', '3')], transitive = [s_one, s_two])",
"s_five = depset(direct = [('1', '3', '5')], transitive = [s_one, s_two])",
"s_six = depset(transitive = [s_one, s_five])",
"s_seven = depset(direct = [('1', '3')], transitive = [s_one, s_five])",
"s_eight = depset(direct = [(1, 3)], transitive = [s_one, s_two])"); // note, tuple of int
assertThat(get("s_one").getContentType()).isEqualTo(SkylarkType.TUPLE);
assertThat(get("s_two").getContentType()).isEqualTo(SkylarkType.TUPLE);
assertThat(get("s_three").getContentType()).isEqualTo(SkylarkType.TUPLE);
assertThat(get("s_eight").getContentType()).isEqualTo(SkylarkType.TUPLE);
assertThat(get("s_four").getSet(Tuple.class))
.containsExactly(
Tuple.of("1", "3"), Tuple.of("1", "2"), Tuple.of("3", "4"), Tuple.of("5", "6"));
assertThat(get("s_five").getSet(Tuple.class))
.containsExactly(
Tuple.of("1", "3", "5"), Tuple.of("1", "2"), Tuple.of("3", "4"), Tuple.of("5", "6"));
assertThat(get("s_eight").getSet(Tuple.class))
.containsExactly(
Tuple.of(1, 3), Tuple.of("1", "2"), Tuple.of("3", "4"), Tuple.of("5", "6"));
}
@Test
public void testGetSet() throws Exception {
exec("s = depset(['a', 'b'])");
assertThat(get("s").getSet(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").getSet(Object.class)).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").getSet(Integer.class));
}
@Test
public void testGetSetDirect() throws Exception {
exec("s = depset(direct = ['a', 'b'])");
assertThat(get("s").getSet(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").getSet(Object.class)).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").getSet(Integer.class));
}
@Test
public void testGetSetItems() throws Exception {
exec("s = depset(items = ['a', 'b'])");
assertThat(get("s").getSet(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").getSet(Object.class)).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").getSet(Integer.class));
}
@Test
public void testToCollection() throws Exception {
exec("s = depset(['a', 'b'])");
assertThat(get("s").toCollection(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection(Object.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection()).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").toCollection(Integer.class));
}
@Test
public void testToCollectionDirect() throws Exception {
exec("s = depset(direct = ['a', 'b'])");
assertThat(get("s").toCollection(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection(Object.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection()).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").toCollection(Integer.class));
}
@Test
public void testToCollectionItems() throws Exception {
exec("s = depset(items = ['a', 'b'])");
assertThat(get("s").toCollection(String.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection(Object.class)).containsExactly("a", "b").inOrder();
assertThat(get("s").toCollection()).containsExactly("a", "b").inOrder();
assertThrows(SkylarkNestedSet.TypeException.class, () -> get("s").toCollection(Integer.class));
}
@Test
public void testOrder() throws Exception {
exec("s = depset(['a', 'b'], order='postorder')");
assertThat(get("s").getSet(String.class).getOrder()).isEqualTo(Order.COMPILE_ORDER);
}
@Test
public void testOrderDirect() throws Exception {
exec("s = depset(direct = ['a', 'b'], order='postorder')");
assertThat(get("s").getSet(String.class).getOrder()).isEqualTo(Order.COMPILE_ORDER);
}
@Test
public void testOrderItems() throws Exception {
exec("s = depset(items = ['a', 'b'], order='postorder')");
assertThat(get("s").getSet(String.class).getOrder()).isEqualTo(Order.COMPILE_ORDER);
}
@Test
public void testBadOrder() throws Exception {
new BothModesTest().testIfExactError(
"Invalid order: non_existing",
"depset(['a'], order='non_existing')");
}
@Test
public void testBadOrderDirect() throws Exception {
new BothModesTest().testIfExactError(
"Invalid order: non_existing",
"depset(direct = ['a'], order='non_existing')");
}
@Test
public void testBadOrderItems() throws Exception {
new BothModesTest().testIfExactError(
"Invalid order: non_existing",
"depset(items = ['a'], order='non_existing')");
}
@Test
public void testEmptyGenericType() throws Exception {
exec("s = depset()");
assertThat(get("s").getContentType()).isEqualTo(SkylarkType.TOP);
}
@Test
public void testHomogeneousGenericType() throws Exception {
exec("s = depset(['a', 'b', 'c'])");
assertThat(get("s").getContentType()).isEqualTo(SkylarkType.of(String.class));
}
@Test
public void testHomogeneousGenericTypeDirect() throws Exception {
exec("s = depset(['a', 'b', 'c'], transitive = [])");
assertThat(get("s").getContentType()).isEqualTo(SkylarkType.of(String.class));
}
@Test
public void testHomogeneousGenericTypeItems() throws Exception {
exec("s = depset(items = ['a', 'b', 'c'], transitive = [])");
assertThat(get("s").getContentType()).isEqualTo(SkylarkType.of(String.class));
}
@Test
public void testHomogeneousGenericTypeTransitive() throws Exception {
exec("s = depset(['a', 'b', 'c'], transitive = [depset(['x'])])");
assertThat(get("s").getContentType()).isEqualTo(SkylarkType.of(String.class));
}
@Test
public void testTransitiveIncompatibleOrder() throws Exception {
checkEvalError(
"Order 'postorder' is incompatible with order 'topological'",
"depset(['a', 'b'], order='postorder',",
" transitive = [depset(['c', 'd'], order='topological')])");
}
@Test
public void testBadGenericType() throws Exception {
new BothModesTest().testIfExactError(
"cannot add an item of type 'int' to a depset of 'string'",
"depset(['a', 5])");
}
@Test
public void testBadGenericTypeDirect() throws Exception {
new BothModesTest().testIfExactError(
"cannot add an item of type 'int' to a depset of 'string'",
"depset(direct = ['a', 5])");
}
@Test
public void testBadGenericTypeItems() throws Exception {
new BothModesTest().testIfExactError(
"cannot add an item of type 'int' to a depset of 'string'",
"depset(items = ['a', 5])");
}
@Test
public void testBadGenericTypeTransitive() throws Exception {
new BothModesTest().testIfExactError(
"cannot add an item of type 'int' to a depset of 'string'",
"depset(['a', 'b'], transitive=[depset([1])])");
}
@Test
public void testLegacyAndNewApi() throws Exception {
new BothModesTest().testIfExactError(
"Do not pass both 'direct' and 'items' argument to depset constructor.",
"depset(['a', 'b'], direct = ['c', 'd'])");
}
@Test
public void testItemsAndTransitive() throws Exception {
new BothModesTest().testIfExactError(
"expected type 'sequence' for items but got type 'depset' instead",
"depset(items = depset(), transitive = [depset()])");
}
@Test
public void testTooManyPositionals() throws Exception {
new BothModesTest()
.testIfErrorContains(
"expected no more than 2 positional arguments, but got 3", "depset([], 'default', [])");
}
@Test
public void testTransitiveOrder() throws Exception {
assertContainsInOrder("depset([], transitive=[depset(['a', 'b', 'c'])])", "a", "b", "c");
assertContainsInOrder("depset(['a'], transitive = [depset(['b', 'c'])])", "b", "c", "a");
assertContainsInOrder("depset(['a', 'b'], transitive = [depset(['c'])])", "c", "a", "b");
assertContainsInOrder("depset(['a', 'b', 'c'], transitive = [depset([])])", "a", "b", "c");
}
@Test
public void testTransitiveOrderItems() throws Exception {
assertContainsInOrder("depset(items=[], transitive=[depset(['a', 'b', 'c'])])", "a", "b", "c");
assertContainsInOrder("depset(items=['a'], transitive = [depset(['b', 'c'])])", "b", "c", "a");
assertContainsInOrder("depset(items=['a', 'b'], transitive = [depset(['c'])])", "c", "a", "b");
assertContainsInOrder("depset(items=['a', 'b', 'c'], transitive = [depset([])])",
"a", "b", "c");
}
@Test
public void testTransitiveOrderDirect() throws Exception {
assertContainsInOrder("depset(direct=[], transitive=[depset(['a', 'b', 'c'])])", "a", "b", "c");
assertContainsInOrder("depset(direct=['a'], transitive = [depset(['b', 'c'])])", "b", "c", "a");
assertContainsInOrder("depset(direct=['a', 'b'], transitive = [depset(['c'])])", "c", "a", "b");
assertContainsInOrder("depset(direct=['a', 'b', 'c'], transitive = [depset([])])",
"a", "b", "c");
}
@Test
public void testIncompatibleUnion() throws Exception {
new BothModesTest("--incompatible_depset_union=true")
.testIfErrorContains(
"depset method `.union` has been removed", "depset([]).union(['a', 'b', 'c'])");
new BothModesTest("--incompatible_depset_union=true")
.testIfErrorContains("`+` operator on a depset is forbidden", "depset([]) + ['a']");
new BothModesTest("--incompatible_depset_union=true")
.testIfErrorContains("`|` operator on a depset is forbidden", "depset([]) | ['a']");
}
@Test
public void testUnionWithList() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
assertContainsInOrder("depset([]).union(['a', 'b', 'c'])", "a", "b", "c");
assertContainsInOrder("depset(['a']).union(['b', 'c'])", "a", "b", "c");
assertContainsInOrder("depset(['a', 'b']).union(['c'])", "a", "b", "c");
assertContainsInOrder("depset(['a', 'b', 'c']).union([])", "a", "b", "c");
}
@Test
public void testUnionWithDepset() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
assertContainsInOrder("depset([]).union(depset(['a', 'b', 'c']))", "a", "b", "c");
assertContainsInOrder("depset(['a']).union(depset(['b', 'c']))", "b", "c", "a");
assertContainsInOrder("depset(['a', 'b']).union(depset(['c']))", "c", "a", "b");
assertContainsInOrder("depset(['a', 'b', 'c']).union(depset([]))", "a", "b", "c");
}
@Test
public void testUnionDuplicates() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
assertContainsInOrder("depset(['a', 'b', 'c']).union(['a', 'b', 'c'])", "a", "b", "c");
assertContainsInOrder("depset(['a', 'a', 'a']).union(['a', 'a'])", "a");
assertContainsInOrder("depset(['a', 'b', 'c']).union(depset(['a', 'b', 'c']))", "a", "b", "c");
assertContainsInOrder("depset(['a', 'a', 'a']).union(depset(['a', 'a']))", "a");
}
private void assertContainsInOrder(String statement, Object... expectedElements)
throws Exception {
assertThat(((SkylarkNestedSet) eval(statement)).toCollection())
.containsExactly(expectedElements)
.inOrder();
}
@Test
public void testUnionOrder() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():",
" s1 = depset()",
" s2 = depset()",
" s1 += ['a']",
" s2 += ['b']",
" s1 += s2",
" return s1",
"s = func()");
assertThat(get("s").toCollection()).containsExactly("b", "a").inOrder();
}
@Test
public void testUnionIncompatibleOrder() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
checkEvalError(
"Order mismatch: topological != postorder",
"depset(['a', 'b'], order='postorder') + depset(['c', 'd'], order='topological')");
}
@Test
public void testUnionWithNonsequence() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
checkEvalError("cannot union value of type 'int' to a depset", "depset([]).union(5)");
checkEvalError("cannot union value of type 'string' to a depset", "depset(['a']).union('b')");
}
@Test
public void testUnionWrongNumArgs() throws Exception {
new BothModesTest()
.testIfErrorContains(
"parameter 'new_elements' has no default value, "
+ "for call to method union(new_elements) of 'depset'",
"depset(['a']).union()");
}
@Test
public void testUnionNoSideEffects() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():",
" s1 = depset(['a'])",
" s2 = s1.union(['b'])",
" return s1",
"s = func()");
assertThat(((SkylarkNestedSet) lookup("s")).toCollection()).isEqualTo(ImmutableList.of("a"));
}
@Test
public void testFunctionReturnsDepset() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():", //
" t = depset()",
" t += ['a']",
" return t",
"s = func()");
assertThat(get("s")).isInstanceOf(SkylarkNestedSet.class);
assertThat(get("s").toCollection()).containsExactly("a");
}
@Test
public void testPlusEqualsWithList() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():", //
" t = depset()",
" t += ['a', 'b']",
" return t",
"s = func()");
assertThat(get("s").toCollection()).containsExactly("a", "b").inOrder();
}
@Test
public void testPlusEqualsNoSideEffects() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():",
" s1 = depset()",
" s1 += ['a']",
" s2 = s1",
" s2 += ['b']",
" return s1",
"s = func()");
assertThat(get("s").toCollection()).containsExactly("a");
}
@Test
public void testFuncParamNoSideEffects() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func1(t):",
" t += ['b']",
"def func2():",
" u = depset()",
" u += ['a']",
" func1(u)",
" return u",
"s = func2()");
assertThat(get("s").toCollection()).containsExactly("a");
}
@Test
public void testTransitiveOrdering() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():",
" sa = depset(['a'], order='postorder')",
" sb = depset(['b'], order='postorder')",
" sc = depset(['c'], order='postorder') + sa",
" return depset() + sb + sc",
"s = func()");
// The iterator lists the Transitive sets first
assertThat(get("s").toCollection()).containsExactly("b", "a", "c").inOrder();
}
@Test
public void testLeftRightDirectOrdering() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"def func():",
" t = depset()",
" t += [4]",
" t += [2, 4]",
" t += [3, 4, 5]",
" return t",
"s = func()");
// All elements are direct. The iterator lists them left-to-right.
assertThat(get("s").toCollection()).containsExactly(4, 2, 3, 5).inOrder();
}
@Test
public void testToString() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"s = depset() + [2, 4, 6] + [3, 4, 5]", //
"x = str(s)");
assertThat(lookup("x")).isEqualTo("depset([2, 4, 6, 3, 5])");
}
@Test
public void testToStringWithOrder() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"s = depset(order = 'topological') + [2, 4, 6] + [3, 4, 5]", //
"x = str(s)");
assertThat(lookup("x")).isEqualTo("depset([2, 4, 6, 3, 5], order = \"topological\")");
}
@SuppressWarnings("unchecked")
private SkylarkNestedSet get(String varname) throws Exception {
return (SkylarkNestedSet) lookup(varname);
}
@Test
public void testToList() throws Exception {
thread = newStarlarkThreadWithSkylarkOptions("--incompatible_depset_union=false");
exec(
"s = depset() + [2, 4, 6] + [3, 4, 5]", //
"x = s.to_list()");
Object value = lookup("x");
assertThat(value).isInstanceOf(StarlarkList.class);
assertThat((Iterable<?>) value).containsExactly(2, 4, 6, 3, 5).inOrder();
}
@Test
public void testOrderCompatibility() throws Exception {
// Two sets are compatible if
// (a) both have the same order or
// (b) at least one order is "default"
for (Order first : Order.values()) {
SkylarkNestedSet s1 = SkylarkNestedSet.of(first, Tuple.of("1", "11"), null);
for (Order second : Order.values()) {
SkylarkNestedSet s2 = SkylarkNestedSet.of(second, Tuple.of("2", "22"), null);
boolean compatible = true;
try {
SkylarkNestedSet.of(s1, s2, null);
} catch (Exception ex) {
compatible = false;
}
assertThat(compatible).isEqualTo(areOrdersCompatible(first, second));
}
}
}
private boolean areOrdersCompatible(Order first, Order second) {
return first == Order.STABLE_ORDER || second == Order.STABLE_ORDER || first == second;
}
@Test
public void testOrderComplexUnion() throws Exception {
// {1, 11, {2, 22}, {3, 33}, {4, 44}}
List<String> preOrder = Arrays.asList("1", "11", "2", "22", "3", "33", "4", "44");
List<String> postOrder = Arrays.asList("2", "22", "3", "33", "4", "44", "1", "11");
MergeStrategy strategy =
new MergeStrategy() {
@Override
public SkylarkNestedSet merge(SkylarkNestedSet[] sets) throws Exception {
SkylarkNestedSet union = SkylarkNestedSet.of(sets[0], sets[1], null);
union = SkylarkNestedSet.of(union, sets[2], null);
union = SkylarkNestedSet.of(union, sets[3], null);
return union;
}
};
runComplexOrderTest(strategy, preOrder, postOrder);
}
@Test
public void testOrderBalancedTree() throws Exception {
// {{1, 11, {2, 22}}, {3, 33, {4, 44}}}
List<String> preOrder = Arrays.asList("1", "11", "2", "22", "3", "33", "4", "44");
List<String> postOrder = Arrays.asList("2", "22", "4", "44", "3", "33", "1", "11");
MergeStrategy strategy =
new MergeStrategy() {
@Override
public SkylarkNestedSet merge(SkylarkNestedSet[] sets) throws Exception {
SkylarkNestedSet leftUnion = SkylarkNestedSet.of(sets[0], sets[1], null);
SkylarkNestedSet rightUnion = SkylarkNestedSet.of(sets[2], sets[3], null);
SkylarkNestedSet union = SkylarkNestedSet.of(leftUnion, rightUnion, null);
return union;
}
};
runComplexOrderTest(strategy, preOrder, postOrder);
}
@Test
public void testOrderManyLevelsOfNesting() throws Exception {
// {1, 11, {2, 22, {3, 33, {4, 44}}}}
List<String> preOrder = Arrays.asList("1", "11", "2", "22", "3", "33", "4", "44");
List<String> postOrder = Arrays.asList("4", "44", "3", "33", "2", "22", "1", "11");
MergeStrategy strategy =
new MergeStrategy() {
@Override
public SkylarkNestedSet merge(SkylarkNestedSet[] sets) throws Exception {
SkylarkNestedSet union = SkylarkNestedSet.of(sets[2], sets[3], null);
union = SkylarkNestedSet.of(sets[1], union, null);
union = SkylarkNestedSet.of(sets[0], union, null);
return union;
}
};
runComplexOrderTest(strategy, preOrder, postOrder);
}
@Test
public void testDepthExceedsLimitDuringIteration() throws Exception {
NestedSet.setApplicationDepthLimit(2000);
new SkylarkTest()
.setUp(
"def create_depset(depth):",
" x = depset([0])",
" for i in range(1, depth):",
" x = depset([i], transitive = [x])",
" for element in x.to_list():",
" str(x)",
" return None")
.testEval("create_depset(1000)", "None")
.testIfErrorContains("depset exceeded maximum depth 2000", "create_depset(3000)");
}
private interface MergeStrategy {
SkylarkNestedSet merge(SkylarkNestedSet[] sets) throws Exception;
}
private void runComplexOrderTest(
MergeStrategy strategy, List<String> preOrder, List<String> postOrder) throws Exception {
Map<Order, List<String>> expected = createExpectedMap(preOrder, postOrder);
for (Order order : Order.values()) {
SkylarkNestedSet union = strategy.merge(makeFourSets(order));
assertThat(union.toCollection()).containsExactlyElementsIn(expected.get(order)).inOrder();
}
}
private Map<Order, List<String>> createExpectedMap(
List<String> preOrder, List<String> postOrder) {
Map<Order, List<String>> expected = new HashMap<>();
for (Order order : Order.values()) {
expected.put(order, isPostOrder(order) ? postOrder : preOrder);
}
return expected;
}
private boolean isPostOrder(Order order) {
return order == Order.STABLE_ORDER || order == Order.COMPILE_ORDER;
}
private SkylarkNestedSet[] makeFourSets(Order order) throws Exception {
return new SkylarkNestedSet[] {
SkylarkNestedSet.of(order, Tuple.of("1", "11"), null),
SkylarkNestedSet.of(order, Tuple.of("2", "22"), null),
SkylarkNestedSet.of(order, Tuple.of("3", "33"), null),
SkylarkNestedSet.of(order, Tuple.of("4", "44"), null)
};
}
}