| // 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)); | |
| } | |
| } |