| // 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 com.google.common.collect.ImmutableList; | |
| import com.google.common.truth.Correspondence; | |
| import com.google.protobuf.CodedInputStream; | |
| import com.google.protobuf.CodedOutputStream; | |
| import java.io.ByteArrayOutputStream; | |
| import java.util.List; | |
| import net.starlark.java.eval.StarlarkThread; | |
| import net.starlark.java.eval.StarlarkThread.CallStackEntry; | |
| import net.starlark.java.syntax.Location; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link CallStack}. */ | |
| @RunWith(JUnit4.class) | |
| public class CallStackTest { | |
| /** | |
| * Compare {@link StarlarkThread.CallStackEntry} using string equality since (1) it doesn't | |
| * currently implement equals and (2) it should have a faithful string representation anyway. | |
| */ | |
| private static final Correspondence<StarlarkThread.CallStackEntry, StarlarkThread.CallStackEntry> | |
| STACK_ENTRY_CORRESPONDENCE = | |
| Correspondence.from( | |
| (l, r) -> l.toString().equals(r.toString()), "String-representations equal"); | |
| @Test | |
| public void testCreateFromEmptyCallStack() { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| CallStack result = factory.createFrom(ImmutableList.of()); | |
| assertThat(result.size()).isEqualTo(0); | |
| assertThat(result.toList()).isEmpty(); | |
| } | |
| @Test | |
| public void testCreateFromSimpleCallStack() { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| ImmutableList<StarlarkThread.CallStackEntry> stack = | |
| ImmutableList.of( | |
| entryFromNameAndLocation("func1", "file1.bzl", 10, 20), | |
| entryFromNameAndLocation("func2", "file2.bzl", 20, 30)); | |
| assertCallStackContents(factory.createFrom(stack), stack); | |
| } | |
| @Test | |
| public void testCreateFromCallStackWithLoops() { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| StarlarkThread.CallStackEntry loopEntry1 = | |
| entryFromNameAndLocation("loop1", "file1.bzl", 10, 20); | |
| StarlarkThread.CallStackEntry loopEntry2 = | |
| entryFromNameAndLocation("loop2", "file2.bzl", 20, 30); | |
| ImmutableList<StarlarkThread.CallStackEntry> stack = | |
| ImmutableList.of(loopEntry1, loopEntry2, loopEntry1, loopEntry2); | |
| assertCallStackContents(factory.createFrom(stack), stack); | |
| } | |
| @Test | |
| public void testCreateFromConsecutiveCalls() { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| ImmutableList.Builder<StarlarkThread.CallStackEntry> stackBuilder = | |
| ImmutableList.<StarlarkThread.CallStackEntry>builder() | |
| .add(entryFromNameAndLocation("f1", "f.bzl", 1, 2)) | |
| .add(entryFromNameAndLocation("g1", "g.bzl", 2, 3)); | |
| ImmutableList<StarlarkThread.CallStackEntry> stack1 = stackBuilder.build(); | |
| ImmutableList<StarlarkThread.CallStackEntry> stack2 = | |
| stackBuilder.add(entryFromNameAndLocation("h1", "h.bzl", 3, 4)).build(); | |
| assertCallStackContents(factory.createFrom(stack1), stack1); | |
| assertCallStackContents(factory.createFrom(stack2), stack2); | |
| } | |
| @Test | |
| public void callStackFactory_tailOptimisation() { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| ImmutableList<StarlarkThread.CallStackEntry> stack1 = | |
| ImmutableList.of( | |
| entryFromNameAndLocation("target1", "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("target2", "b/BUILD", 6, 7), | |
| entryFromNameAndLocation("java_library_macro", "java_library_macro.bzl", 2, 3), | |
| entryFromNameAndLocation("java_library", "java_library.bzl", 4, 5)); | |
| CallStack optimisedStack1 = factory.createFrom(stack1); | |
| CallStack optimisedStack2 = factory.createFrom(stack2); | |
| assertCallStackContents(optimisedStack1, stack1); | |
| assertCallStackContents(optimisedStack2, stack2); | |
| assertThat(optimisedStack1.head.child).isSameInstanceAs(optimisedStack2.head.child); | |
| assertThat(optimisedStack1.head.child.child).isSameInstanceAs(optimisedStack2.head.child.child); | |
| } | |
| @Test | |
| public void testSerialization() throws Exception { | |
| CallStack.Factory factory = new CallStack.Factory(); | |
| ImmutableList<StarlarkThread.CallStackEntry> stackEntries1 = | |
| ImmutableList.of( | |
| 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("shortStack", "short.bzl", 9, 10)); | |
| CallStack callStack1 = factory.createFrom(stackEntries1); | |
| CallStack callStack2 = factory.createFrom(stackEntries2); | |
| CallStack.Serializer serializer = new CallStack.Serializer(); | |
| ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); | |
| CodedOutputStream codedOut = CodedOutputStream.newInstance(bytesOut); | |
| serializer.prepareCallStack(callStack1); | |
| serializer.prepareCallStack(callStack2); | |
| serializer.serializeCallStack(callStack1, codedOut); | |
| serializer.serializeCallStack(callStack2, codedOut); | |
| serializer.serializeCallStack(callStack1, codedOut); | |
| codedOut.flush(); | |
| CallStack.Deserializer deserializer = new CallStack.Deserializer(); | |
| CodedInputStream codedIn = CodedInputStream.newInstance(bytesOut.toByteArray()); | |
| CallStack deserializedCallStack1 = deserializer.deserializeCallStack(codedIn); | |
| assertCallStackContents(deserializedCallStack1, stackEntries1); | |
| CallStack deserializedCallStack2 = deserializer.deserializeCallStack(codedIn); | |
| assertCallStackContents(deserializedCallStack2, stackEntries2); | |
| CallStack deserializedCallStack1Again = deserializer.deserializeCallStack(codedIn); | |
| assertCallStackContents(deserializedCallStack1Again, stackEntries1); | |
| } | |
| /** Asserts the provided {@link CallStack} faithfully represents the expected stack. */ | |
| private static void assertCallStackContents(CallStack result, List<CallStackEntry> expected) { | |
| assertThat(result.size()).isEqualTo(expected.size()); | |
| assertThat(result.toList()) | |
| .comparingElementsUsing(STACK_ENTRY_CORRESPONDENCE) | |
| .containsExactlyElementsIn(expected) | |
| .inOrder(); | |
| // toList and getFrame use different code paths, make sure they agree. | |
| for (int i = 0; i < expected.size(); i++) { | |
| assertThat(result.getFrame(i).toString()).isEqualTo(expected.get(i).toString()); | |
| } | |
| } | |
| private static StarlarkThread.CallStackEntry entryFromNameAndLocation( | |
| String name, String file, int line, int col) { | |
| return new CallStackEntry(name, Location.fromFileLineColumn(file, line, col)); | |
| } | |
| } |