blob: ea85fdb1687bad641b18e07eb6581d47e507e359 [file] [log] [blame]
// Copyright 2021 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.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecs;
import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Location;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
/** Tests for {@link CallStack}. */
@RunWith(JUnit4.class)
public final class CallStackTest {
@Test
public void emptyCallStack_null() {
assertThat(CallStack.compact(ImmutableList.of(), 0)).isNull();
assertThat(CallStack.compact(ImmutableList.of(), 1)).isNull();
assertThat(CallStack.compact(ImmutableList.of(), 42)).isNull();
}
@Test
public void singleFrameCallStack_nullInterior() {
ImmutableList<StarlarkThread.CallStackEntry> stack =
ImmutableList.of(entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 10, 20));
CallStack.Node compacted = CallStack.compact(stack, 0);
assertThat(compacted).isNotNull();
assertThat(compacted.next()).isNull();
assertThat(CallStack.compact(stack, 1)).isNull();
assertThat(CallStack.compact(stack, 42)).isNull();
}
@Test
public void simpleCallStack() {
ImmutableList<StarlarkThread.CallStackEntry> stack =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 10, 20),
entryFromNameAndLocation("func", "file.bzl", 20, 30));
CallStack.Node compacted0 = CallStack.compact(stack, 0);
CallStack.Node compacted1 = CallStack.compact(stack, 1);
assertThat(compacted0.next()).isEqualTo(compacted1);
assertThat(compacted1.next()).isNull();
assertCallStackContents(compacted0, stack, 0);
assertCallStackContents(compacted1, stack, 1);
assertThat(CallStack.compact(stack, 2)).isNull();
}
@Test
public void callStackWithLoops() {
StarlarkThread.CallStackEntry loopEntry1 =
entryFromNameAndLocation("loop1", "file1.bzl", 20, 30);
StarlarkThread.CallStackEntry loopEntry2 =
entryFromNameAndLocation("loop2", "file2.bzl", 30, 40);
ImmutableList<StarlarkThread.CallStackEntry> stack =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 10, 20),
loopEntry1,
loopEntry2,
loopEntry1,
loopEntry2);
assertCallStackContents(CallStack.compact(stack, 1), stack, 1);
}
@Test
public void consecutiveCalls() {
ImmutableList.Builder<StarlarkThread.CallStackEntry> stackBuilder =
ImmutableList.<StarlarkThread.CallStackEntry>builder()
.add(entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 1, 2))
.add(entryFromNameAndLocation("f1", "f.bzl", 2, 3))
.add(entryFromNameAndLocation("g1", "g.bzl", 3, 4));
ImmutableList<StarlarkThread.CallStackEntry> stack1 = stackBuilder.build();
ImmutableList<StarlarkThread.CallStackEntry> stack2 =
stackBuilder.add(entryFromNameAndLocation("h1", "h.bzl", 4, 5)).build();
assertCallStackContents(CallStack.compact(stack1, 1), stack1, 1);
assertCallStackContents(CallStack.compact(stack2, 1), stack2, 1);
}
@Test
public void sharesCommonTail() {
ImmutableList<StarlarkThread.CallStackEntry> stack1 =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "a/BUILD", 1, 2),
entryFromNameAndLocation("java_library_macro", "java_library_macro.bzl", 2, 3),
entryFromNameAndLocation("java_library", "java_library.bzl", 4, 5));
ImmutableList<StarlarkThread.CallStackEntry> stack2 =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "b/BUILD", 6, 7),
entryFromNameAndLocation("java_library_macro", "java_library_macro.bzl", 2, 3),
entryFromNameAndLocation("java_library", "java_library.bzl", 4, 5));
CallStack.Node optimizedInteriorStack1 = CallStack.compact(stack1, 1);
CallStack.Node optimizedInteriorStack2 = CallStack.compact(stack2, 1);
CallStack.Node optimizedFullStack1 = CallStack.compact(stack1, 0);
CallStack.Node optimizedFullStack2 = CallStack.compact(stack2, 0);
assertCallStackContents(optimizedInteriorStack1, stack1, 1);
assertCallStackContents(optimizedInteriorStack2, stack2, 1);
assertCallStackContents(optimizedFullStack1, stack1, 0);
assertCallStackContents(optimizedFullStack2, stack2, 0);
assertThat(optimizedInteriorStack2.next().next())
.isSameInstanceAs(optimizedInteriorStack2.next().next());
assertThat(optimizedFullStack1.next()).isSameInstanceAs(optimizedFullStack2.next());
assertThat(optimizedFullStack1.next()).isSameInstanceAs(optimizedInteriorStack1);
}
@Test
public void serialization() throws Exception {
ImmutableList<StarlarkThread.CallStackEntry> stackEntries1 =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 1, 2),
entryFromNameAndLocation("somename", "f1.bzl", 1, 2),
entryFromNameAndLocation("someOtherName", "f2.bzl", 2, 4),
entryFromNameAndLocation("somename", "f1.bzl", 4, 2),
entryFromNameAndLocation("somethingElse", "f3.bzl", 5, 6));
ImmutableList<StarlarkThread.CallStackEntry> stackEntries2 =
ImmutableList.of(entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 9, 10));
CallStack.Node interiorStack1 = CallStack.compact(stackEntries1, 1);
CallStack.Node interiorStack2 = CallStack.compact(stackEntries2, 1);
RuleClass ruleClass = mock(RuleClass.class);
Mockito.when(ruleClass.getAttributeProvider()).thenReturn(mock(AttributeProvider.class));
Rule rule1 =
new Rule(
mock(Package.class),
Label.parseCanonicalUnchecked("//pkg:rule1"),
ruleClass,
stackEntries1.get(0).location,
interiorStack1);
Rule rule2 =
new Rule(
mock(Package.class),
Label.parseCanonicalUnchecked("//pkg:rule2"),
ruleClass,
stackEntries2.get(0).location,
interiorStack2);
SerializationContext serializer =
new ObjectCodecs().getMemoizingSerializationContextForTesting();
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
CodedOutputStream codedOut = CodedOutputStream.newInstance(bytesOut);
serializer.serialize(CallStack.getFullCallStack(rule1), codedOut);
serializer.serialize(CallStack.getFullCallStack(rule2), codedOut);
serializer.serialize(CallStack.getFullCallStack(rule1), codedOut);
codedOut.flush();
DeserializationContext deserializer =
new ObjectCodecs().getMemoizingDeserializationContextForTesting();
CodedInputStream codedIn = CodedInputStream.newInstance(bytesOut.toByteArray());
CallStack.Node deserializedCallStack1 = deserializer.deserialize(codedIn);
assertThat(deserializedCallStack1.toLocation()).isEqualTo(rule1.getLocation());
assertCallStackContents(deserializedCallStack1.next(), stackEntries1, 1);
CallStack.Node deserializedCallStack2 = deserializer.deserialize(codedIn);
assertThat(deserializedCallStack2.toLocation()).isEqualTo(rule2.getLocation());
assertCallStackContents(deserializedCallStack2.next(), stackEntries2, 1);
CallStack.Node deserializedCallStack1Again = deserializer.deserialize(codedIn);
assertThat(deserializedCallStack1Again.toLocation()).isEqualTo(rule1.getLocation());
assertCallStackContents(deserializedCallStack1Again.next(), stackEntries1, 1);
}
@Test
public void concatenate() {
ImmutableList<StarlarkThread.CallStackEntry> outerStack =
ImmutableList.of(
entryFromNameAndLocation(StarlarkThread.TOP_LEVEL, "BUILD", 10, 20),
entryFromNameAndLocation("foo", "f.bzl", 1, 2),
entryFromNameAndLocation("bar", "g.bzl", 3, 4));
ImmutableList<StarlarkThread.CallStackEntry> innerStack =
ImmutableList.of(
entryFromNameAndLocation("baz", "h.bzl", 5, 6),
entryFromNameAndLocation("qux", "g.bzl", 7, 8));
assertThat(CallStack.concatenate(null, null)).isNull();
assertCallStackContents(
CallStack.concatenate(CallStack.compact(outerStack, 1), null), outerStack, 1);
assertCallStackContents(
CallStack.concatenate(null, CallStack.compact(innerStack, 0)), innerStack, 0);
assertCallStackContents(
CallStack.concatenate(CallStack.compact(outerStack, 1), CallStack.compact(innerStack, 0)),
ImmutableList.<StarlarkThread.CallStackEntry>builder()
.addAll(outerStack)
.addAll(innerStack)
.build(),
1);
}
/**
* Asserts the provided {@link CallStack.Node} faithfully represents the expected stack, ignoring
* {@code expectedStart} of the expected stack's outer frames.
*/
private static void assertCallStackContents(
CallStack.Node compacted, List<StarlarkThread.CallStackEntry> expected, int expectedStart) {
List<StarlarkThread.CallStackEntry> reconstituted = new ArrayList<>();
for (CallStack.Node node = compacted; node != null; node = node.next()) {
reconstituted.add(node.toCallStackEntry());
}
assertThat(reconstituted).isEqualTo(expected.subList(expectedStart, expected.size()));
}
private static StarlarkThread.CallStackEntry entryFromNameAndLocation(
String name, String file, int line, int col) {
return StarlarkThread.callStackEntry(name, Location.fromFileLineColumn(file, line, col));
}
}