| // Copyright 2018 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.importdeps; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.importdeps.AbstractClassEntryState.ExistingState; |
| import com.google.devtools.build.importdeps.ClassCache.LazyClassEntry; |
| import com.google.devtools.build.importdeps.ClassCache.LazyClasspath; |
| import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; |
| import java.io.IOException; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Test for {@link ClassCache}. */ |
| @RunWith(JUnit4.class) |
| public class ClassCacheTest extends AbstractClassCacheTest { |
| |
| @Test |
| public void testLibraryJar() throws Exception { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(), |
| /*populateMembers=*/ true)) { |
| assertCache( |
| cache, |
| libraryJarPositives, |
| combine(libraryAnnotationsJarPositives, libraryExceptionJarPositives)); |
| } |
| } |
| |
| @Test |
| public void testClientJarWithSuperClasses() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| assertCache( |
| cache, |
| clientJarPositives, |
| combine(libraryExceptionJarPositives, libraryAnnotationsJarPositives)); |
| } |
| } |
| |
| @Test |
| public void testClientJarWithoutSuperClasses() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| // Client should be incomplete, as its parent class and interfaces are not available on the |
| // classpath. The following is the resolution path. |
| { |
| AbstractClassEntryState state = cache.getClassState(PACKAGE_NAME + "Client"); |
| assertThat(state.isIncompleteState()).isTrue(); |
| |
| ImmutableSet<String> successPart = |
| state.asIncompleteState().resolutionFailureChain().getMissingClassesWithSubclasses() |
| .values().stream() |
| .distinct() |
| .sorted() |
| .map(ClassInfo::internalName) |
| .collect(ImmutableSet.toImmutableSet()); |
| assertThat(successPart).containsExactly(PACKAGE_NAME + "Client").inOrder(); |
| assertThat(state.asIncompleteState().missingAncestors()) |
| .containsExactly( |
| PACKAGE_NAME + "Library", |
| PACKAGE_NAME + "LibraryInterface", |
| PACKAGE_NAME + "LibraryInterface$One", |
| PACKAGE_NAME + "LibraryInterface$Two"); |
| } |
| assertThat(cache.getClassState(PACKAGE_NAME + "Client").isIncompleteState()).isTrue(); |
| assertThat(cache.getClassState(PACKAGE_NAME + "Client$NestedAnnotation")) |
| .isInstanceOf(ExistingState.class); |
| assertThat(cache.getClassState(PACKAGE_NAME + "Client$NestedAnnotation").isExistingState()) |
| .isTrue(); |
| assertThat(cache.getClassState("java/lang/Object").isExistingState()).isTrue(); |
| assertThat(cache.getClassState("java/util/List").isExistingState()).isTrue(); |
| } |
| } |
| |
| @Test |
| public void testLibraryException() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| ImmutableSet.of(libraryExceptionJar), |
| /*populateMembers=*/ true)) { |
| assertCache( |
| cache, |
| libraryExceptionJarPositives, |
| combine(libraryAnnotationsJarPositives, libraryInterfacePositives, libraryJarPositives)); |
| } |
| } |
| |
| @Test |
| public void testLibraryAnnotations() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| ImmutableSet.of(libraryAnnotationsJar), |
| /*populateMembers=*/ true)) { |
| assertCache( |
| cache, |
| libraryAnnotationsJarPositives, |
| combine(libraryExceptionJarPositives, libraryInterfacePositives, libraryJarPositives)); |
| } |
| } |
| |
| @Test |
| public void testCannotAccessClosedCache() throws IOException { |
| ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| ImmutableSet.of(), |
| /*populateMembers=*/ true); |
| cache.close(); |
| cache.close(); // Can close multiple times. |
| assertThrows(IllegalStateException.class, () -> cache.getClassState("empty")); |
| } |
| |
| /** |
| * A regression test. First query the super class, which does not exist. Then query the subclass, |
| * which does not exist either. |
| */ |
| @Test |
| public void testSuperNotExistThenSubclassNotExist() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(), |
| ImmutableSet.of(libraryJar, libraryJar, libraryAnnotationsJar, libraryInterfaceJar), |
| ImmutableSet.of(libraryJar, libraryJar, libraryAnnotationsJar, libraryInterfaceJar), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/Library$Class9") |
| .isIncompleteState()) |
| .isTrue(); |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/Library$Class10") |
| .isIncompleteState()) |
| .isTrue(); |
| } |
| } |
| |
| @Test |
| public void testJdepsOutput() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(), |
| ImmutableSet.of(libraryJar, libraryJar, libraryAnnotationsJar, libraryInterfaceJar), |
| ImmutableSet.of(libraryJar, libraryJar, libraryAnnotationsJar, libraryInterfaceJar), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/Library$Class9") |
| .isIncompleteState()) |
| .isTrue(); |
| assertThat(cache.collectUsedJarsInRegularClasspath()).containsExactly(libraryJar, true); |
| |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/LibraryAnnotations") |
| .isIncompleteState()) |
| .isTrue(); |
| assertThat(cache.collectUsedJarsInRegularClasspath()) |
| .containsExactly(libraryJar, true, libraryAnnotationsJar, true); |
| |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/LibraryInterface") |
| .isIncompleteState()) |
| .isTrue(); |
| assertThat(cache.collectUsedJarsInRegularClasspath()) |
| .containsExactly( |
| libraryJar, true, libraryAnnotationsJar, true, libraryInterfaceJar, true); |
| } |
| } |
| |
| @Test |
| public void testJdepsOutput_withSuperclasses_hasImplicitDeps() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| |
| assertThat( |
| cache |
| .getClassState("com/google/devtools/build/importdeps/testdata/Client") |
| .isExistingState()) |
| .isTrue(); |
| assertThat(cache.collectUsedJarsInRegularClasspath()) |
| .containsExactly(libraryJar, false, libraryInterfaceJar, false); |
| } |
| } |
| |
| @Test |
| public void testLazyClasspathLoadsBootclasspathFirst() throws IOException { |
| { |
| LazyClasspath classpath = |
| new LazyClasspath( |
| ImmutableSet.of(libraryJar), |
| ImmutableSet.of(libraryWoMembersJar), |
| ImmutableSet.of(libraryWoMembersJar), |
| ImmutableSet.of(), |
| /*populateMembers=*/ true); |
| LazyClassEntry entry = |
| classpath.getLazyEntry("com/google/devtools/build/importdeps/testdata/Library$Class4"); |
| AbstractClassEntryState state = entry.getState(classpath); |
| Optional<ClassInfo> classInfo = state.classInfo(); |
| assertThat(classInfo.isPresent()).isTrue(); |
| assertThat(classInfo.get().declaredMembers()).hasSize(2); |
| assertThat( |
| classInfo |
| .get() |
| .declaredMembers() |
| .stream() |
| .map(MemberInfo::memberName) |
| .collect(Collectors.toSet())) |
| .containsExactly("<init>", "createClass5"); |
| } |
| { |
| LazyClasspath classpath = |
| new LazyClasspath( |
| ImmutableSet.of(libraryWoMembersJar), |
| ImmutableSet.of(libraryJar), |
| ImmutableSet.of(libraryJar), |
| ImmutableSet.of(), |
| /*populateMembers=*/ true); |
| LazyClassEntry entry = |
| classpath.getLazyEntry("com/google/devtools/build/importdeps/testdata/Library$Class4"); |
| AbstractClassEntryState state = entry.getState(classpath); |
| Optional<ClassInfo> classInfo = state.classInfo(); |
| assertThat(classInfo.isPresent()).isTrue(); |
| // The empty class has a default constructor. |
| assertThat(classInfo.get().declaredMembers()).hasSize(1); |
| assertThat(classInfo.get().declaredMembers().iterator().next().memberName()) |
| .isEqualTo("<init>"); |
| } |
| } |
| |
| @Test |
| public void testLazyClasspath_skipMembers() throws IOException { |
| LazyClasspath classpath = |
| new LazyClasspath( |
| ImmutableSet.of(libraryJar), |
| ImmutableSet.of(libraryWoMembersJar), |
| ImmutableSet.of(libraryWoMembersJar), |
| ImmutableSet.of(), |
| /*populateMembers=*/ false); |
| LazyClassEntry entry = |
| classpath.getLazyEntry("com/google/devtools/build/importdeps/testdata/Library$Class4"); |
| AbstractClassEntryState state = entry.getState(classpath); |
| Optional<ClassInfo> classInfo = state.classInfo(); |
| assertThat(classInfo.isPresent()).isTrue(); |
| assertThat(classInfo.get().declaredMembers()).isEmpty(); |
| } |
| |
| @Test |
| public void testOriginInformation() throws IOException { |
| try (ClassCache cache = |
| new ClassCache( |
| ImmutableSet.of(bootclasspath), |
| ImmutableSet.of(libraryJar), |
| ImmutableSet.of(libraryJar, libraryInterfaceJar), |
| ImmutableSet.of(clientJar), |
| /*populateMembers=*/ true)) { |
| assertCache( |
| cache, |
| clientJarPositives, |
| combine(libraryExceptionJarPositives, libraryAnnotationsJarPositives)); |
| |
| { |
| AbstractClassEntryState state = cache.getClassState(PACKAGE_NAME + "Library"); |
| assertThat(state.isExistingState()).isTrue(); |
| assertThat(state.classInfo().isPresent()).isTrue(); |
| assertThat(state.classInfo().get().directDep()).isTrue(); |
| assertThat((Object) state.classInfo().get().jarPath()).isEqualTo(libraryJar); |
| } |
| { |
| AbstractClassEntryState state = cache.getClassState(PACKAGE_NAME + "LibraryInterface"); |
| assertThat(state.isExistingState()).isTrue(); |
| assertThat(state.classInfo().isPresent()).isTrue(); |
| assertThat(state.classInfo().get().directDep()).isFalse(); |
| assertThat((Object) state.classInfo().get().jarPath()).isEqualTo(libraryInterfaceJar); |
| } |
| } |
| } |
| |
| private void assertCache( |
| ClassCache cache, ImmutableList<String> positives, ImmutableList<String> negatives) { |
| for (String positive : positives) { |
| AbstractClassEntryState state = cache.getClassState(positive); |
| assertWithMessage(positive).that(state.isExistingState()).isTrue(); |
| assertWithMessage(positive).that(state.asExistingState()).isInstanceOf(ExistingState.class); |
| assertWithMessage(positive) |
| .that(state.asExistingState().classInfo().get().internalName()) |
| .isEqualTo(positive); |
| } |
| for (String negative : negatives) { |
| AbstractClassEntryState state = cache.getClassState(negative); |
| assertWithMessage(negative).that(state.isExistingState()).isFalse(); |
| } |
| } |
| } |