blob: 62cb6657275d529eb4840fe53369f41bb1188fa8 [file] [log] [blame]
// Copyright 2017 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.packages;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Location;
import com.google.devtools.build.lib.syntax.StarlarkValue;
import com.google.devtools.build.lib.syntax.TokenKind;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.annotation.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Test class for {@link StarlarkInfo} and its subclasses. */
@RunWith(JUnit4.class)
public class StarlarkInfoTest {
@Test
public void nullLocationDefaultsToBuiltin() throws Exception {
StarlarkInfo info = StarlarkInfo.create(makeProvider(), ImmutableMap.of(), null);
assertThat(info.getCreationLoc()).isEqualTo(Location.BUILTIN);
}
@Test
public void instancesOfUnexportedProvidersAreMutable() throws Exception {
StarlarkProvider provider = makeProvider();
StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, null);
assertThat(info.isImmutable()).isFalse();
}
@Test
public void instancesOfExportedProvidersMayBeImmutable() throws Exception {
StarlarkProvider provider = makeExportedProvider();
StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, null);
assertThat(info.isImmutable()).isTrue();
}
@Test
public void mutableIfContentsAreMutable() throws Exception {
StarlarkProvider provider = makeExportedProvider();
StarlarkValue v = new StarlarkValue() {};
StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, v);
assertThat(info.isImmutable()).isFalse();
}
@Test
public void equivalence() throws Exception {
StarlarkProvider provider1 = makeProvider();
StarlarkProvider provider2 = makeProvider();
// equal providers and fields
assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
.isEqualTo(makeInfoWithF1F2Values(provider1, 4, 5));
// different providers => unequal
assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
.isNotEqualTo(makeInfoWithF1F2Values(provider2, 4, 5));
// different fields => unequal
assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
.isNotEqualTo(makeInfoWithF1F2Values(provider1, 4, 6));
// different sets of fields => unequal
assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
.isNotEqualTo(makeInfoWithF1F2Values(provider1, 4, null));
}
@Test
public void concatWithDifferentProvidersFails() throws Exception {
StarlarkProvider provider1 = makeProvider();
StarlarkProvider provider2 = makeProvider();
StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, 4, 5);
StarlarkInfo info2 = makeInfoWithF1F2Values(provider2, 4, 5);
EvalException expected =
assertThrows(EvalException.class, () -> info1.binaryOp(TokenKind.PLUS, info2, true));
assertThat(expected).hasMessageThat()
.contains("Cannot use '+' operator on instances of different providers");
}
@Test
public void concatWithOverlappingFieldsFails() throws Exception {
StarlarkProvider provider1 = makeProvider();
StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, 4, 5);
StarlarkInfo info2 = makeInfoWithF1F2Values(provider1, 4, null);
EvalException expected =
assertThrows(EvalException.class, () -> info1.binaryOp(TokenKind.PLUS, info2, true));
assertThat(expected)
.hasMessageThat()
.contains("cannot add struct instances with common field 'f1'");
}
@Test
public void concatWithSameFields() throws Exception {
StarlarkProvider provider = makeProvider();
StarlarkInfo info1 = makeInfoWithF1F2Values(provider, 4, null);
StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, 5);
StarlarkInfo result = info1.binaryOp(TokenKind.PLUS, info2, true);
assertThat(result.getFieldNames()).containsExactly("f1", "f2");
assertThat(result.getValue("f1")).isEqualTo(4);
assertThat(result.getValue("f2")).isEqualTo(5);
}
@Test
public void concatWithDifferentFields() throws Exception {
StarlarkProvider provider = makeProvider();
StarlarkInfo info1 = makeInfoWithF1F2Values(provider, 4, null);
StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, 5);
StarlarkInfo result = info1.binaryOp(TokenKind.PLUS, info2, true);
assertThat(result.getFieldNames()).containsExactly("f1", "f2");
assertThat(result.getValue("f1")).isEqualTo(4);
assertThat(result.getValue("f2")).isEqualTo(5);
}
/** Creates an unexported schemaless provider type with builtin location. */
private static StarlarkProvider makeProvider() {
return StarlarkProvider.createUnexportedSchemaless(Location.BUILTIN);
}
/** Creates an exported schemaless provider type with builtin location. */
private static StarlarkProvider makeExportedProvider() {
StarlarkProvider.Key key =
new StarlarkProvider.Key(Label.parseAbsoluteUnchecked("//package:target"), "provider");
return StarlarkProvider.createExportedSchemaless(key, Location.BUILTIN);
}
/**
* Creates an instance of a provider with the given values for fields f1 and f2. Either field
* value may be null, in which case it is omitted.
*/
private static StarlarkInfo makeInfoWithF1F2Values(
StarlarkProvider provider, @Nullable Object v1, @Nullable Object v2) {
ImmutableMap.Builder<String, Object> values = ImmutableMap.builder();
if (v1 != null) {
values.put("f1", v1);
}
if (v2 != null) {
values.put("f2", v2);
}
return StarlarkInfo.create(provider, values.build(), Location.BUILTIN);
}
// Tests Ganapathy permute algorithm on arrays of various lengths from Fibonacci sequence.
@Test
public void testPermute() throws Exception {
boolean ok = true;
// (a, b) is the Fibonacci generator. We use a as the array length.
for (int a = 0, b = 1; a < 1000; ) {
// generate array of 'a' k/v pairs
Integer[] array = new Integer[2 * a];
for (int i = 0; i < a; i++) {
array[2 * i] = i + 1; // keys are positive
array[2 * i + 1] = -i - 1; // value is negation of corresponding key
}
StarlarkInfo.permute(array);
// Assert that keys (positive) appear before values (negative).
for (int i = 0; i < 2 * a; i++) {
if ((i < a) != (array[i] > 0)) {
System.err.printf(
"a=%d: at index %d, keys not before values: %s\n", a, i, Arrays.toString(array));
ok = false;
break;
}
}
// Assert that key/value correspondence is maintained.
for (int i = 0; i < a; i++) {
int k = array[i];
int v = array[i + a];
if (k != -v) {
System.err.printf(
"a=%d: at index %d, key=%d but value=%d, want %d: %s\n",
a, i, k, v, -k, Arrays.toString(array));
ok = false;
break;
}
}
// Assert that all keys in input remain present in output.
Integer[] sortedKeys = Arrays.copyOf(array, a);
Arrays.sort(sortedKeys);
for (int i = 0; i < a; i++) {
if (sortedKeys[i] != i + 1) {
System.err.printf(
"a=%d: at index %d of sorted keys, got %d, want %d: %s\n",
a, i, sortedKeys[i], i + 1, Arrays.toString(sortedKeys));
ok = false;
break;
}
}
// next Fibonacci number
int c = a + b;
a = b;
b = c;
}
if (!ok) {
throw new AssertionError("failed");
}
}
// Tests sortPairs using arrays of various lengths from Fibonacci sequence.
@Test
public void testSortPairs() throws Exception {
boolean ok = true;
Random rand = new Random(0);
// (a, b) is the Fibonacci generator. We use a as the array length.
for (int a = 0, b = 1; a < 1000; ) {
// generate random array of a pairs.
Object[] array = new Object[2 * a];
for (int i = 0; i < a; i++) {
int r = rand.nextInt(1000000);
array[i] = String.format("key%06d", r);
array[a + i] = r;
}
// Sort keys and values using reference implementation.
@SuppressWarnings("unchecked")
List<String> origKeys =
(List<String>) (List<?>) new ArrayList<>(Arrays.asList(array).subList(0, a));
Collections.sort(origKeys);
@SuppressWarnings("unchecked")
List<Integer> origValues =
(List<Integer>) (List<?>) new ArrayList<>(Arrays.asList(array).subList(a, 2 * a));
Collections.sort(origValues);
// Sort using sortPairs.
if (a > 0) {
StarlarkInfo.sortPairs(array, 0, a - 1);
}
// Assert sorted keys match reference implementation.
List<?> keys = Arrays.asList(array).subList(0, a);
if (!keys.equals(origKeys)) {
System.err.printf("a=%d: keys not in order: got %s, want %s\n", a, keys, origKeys);
ok = false;
}
// Assert sorted values match reference implementation.
List<?> values = Arrays.asList(array).subList(a, 2 * a);
if (!values.equals(origValues)) {
System.err.printf("a=%d: values not in order: got %s, want %s\n", a, values, origValues);
ok = false;
}
// next Fibonacci number
int c = a + b;
a = b;
b = c;
}
if (!ok) {
throw new AssertionError("failed");
}
}
}