blob: 5093f6a85c0dd4afabdc05a943e74b6001dceef7 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. 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.turbine.binder;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Locale.ENGLISH;
import static org.junit.Assert.fail;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.MoreFiles;
import com.google.turbine.binder.bound.EnumConstantValue;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.lookup.LookupKey;
import com.google.turbine.binder.lookup.LookupResult;
import com.google.turbine.binder.lookup.PackageScope;
import com.google.turbine.binder.lookup.Scope;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type.ClassTy;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ClassPathBinderTest {
@Parameterized.Parameters
public static ImmutableCollection<Object[]> parameters() {
Object[] testCases = {
TURBINE_BOOTCLASSPATH,
FileManagerClassBinder.adapt(
ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8),
StandardLocation.PLATFORM_CLASS_PATH),
};
return Arrays.stream(testCases).map(x -> new Object[] {x}).collect(toImmutableList());
}
private final ClassPath classPath;
public ClassPathBinderTest(ClassPath classPath) {
this.classPath = classPath;
}
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
private static Ident ident(String string) {
return new Ident(/* position= */ -1, string);
}
@Test
public void classPathLookup() {
Scope javaLang = classPath.index().lookupPackage(ImmutableList.of("java", "lang"));
final String string = "String";
LookupResult result = javaLang.lookup(new LookupKey(ImmutableList.of(ident(string))));
assertThat(result.remaining()).isEmpty();
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/String"));
result = javaLang.lookup(new LookupKey(ImmutableList.of(ident("Object"))));
assertThat(result.remaining()).isEmpty();
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/Object"));
}
@Test
public void packageScope() {
PackageScope result = classPath.index().lookupPackage(ImmutableList.of("java", "nosuch"));
assertThat(result).isNull();
result = classPath.index().lookupPackage(ImmutableList.of("java", "lang"));
assertThat(result.classes()).contains(new ClassSymbol("java/lang/String"));
assertThat(result.lookup(new LookupKey(ImmutableList.of(ident("NoSuch"))))).isNull();
}
@Test
public void scope() {
Scope scope = classPath.index().scope();
LookupResult result;
result =
scope.lookup(
new LookupKey(
ImmutableList.of(ident("java"), ident("util"), ident("Map"), ident("Entry"))));
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
assertThat(result.remaining().stream().map(Ident::value)).containsExactly("Entry");
result =
scope.lookup(new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("Map"))));
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
assertThat(result.remaining()).isEmpty();
result =
scope.lookup(
new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("NoSuch"))));
assertThat(result).isNull();
}
@Test
public void classPathClasses() {
Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/util/Map$Entry"));
assertThat(c.owner()).isEqualTo(new ClassSymbol("java/util/Map"));
assertThat(c.kind()).isEqualTo(TurbineTyKind.INTERFACE);
assertThat(env.get(new ClassSymbol("javax/lang/model/SourceVersion")).kind())
.isEqualTo(TurbineTyKind.ENUM);
assertThat(env.get(new ClassSymbol("java/lang/String")).kind()).isEqualTo(TurbineTyKind.CLASS);
assertThat(env.get(new ClassSymbol("java/lang/Override")).kind())
.isEqualTo(TurbineTyKind.ANNOTATION);
c = env.get(new ClassSymbol("java/util/ArrayList"));
assertThat((c.access() & TurbineFlag.ACC_PUBLIC)).isEqualTo(TurbineFlag.ACC_PUBLIC);
assertThat(c.superclass()).isEqualTo(new ClassSymbol("java/util/AbstractList"));
assertThat(c.interfaces()).contains(new ClassSymbol("java/util/List"));
assertThat(c.owner()).isNull();
}
@Test
public void interfaces() {
Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention"));
assertThat(c.interfaceTypes()).hasSize(1);
assertThat(((ClassTy) getOnlyElement(c.interfaceTypes())).sym())
.isEqualTo(new ClassSymbol("java/lang/annotation/Annotation"));
c = env.get(new ClassSymbol("java/util/ArrayList"));
ClassTy listInterface =
(ClassTy)
c.interfaceTypes().stream()
.filter(i -> ((ClassTy) i).sym().equals(new ClassSymbol("java/util/List")))
.collect(onlyElement());
assertThat(getLast(listInterface.classes()).targs()).hasSize(1);
}
@Test
public void annotations() {
Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention"));
AnnoInfo anno =
c.annotations().stream()
.filter(a -> a.sym().equals(new ClassSymbol("java/lang/annotation/Retention")))
.collect(onlyElement());
assertThat(anno.values().keySet()).containsExactly("value");
assertThat(((EnumConstantValue) anno.values().get("value")).sym())
.isEqualTo(
new FieldSymbol(new ClassSymbol("java/lang/annotation/RetentionPolicy"), "RUNTIME"));
}
@Test
public void byteCodeBoundClassName() {
BytecodeBoundClass c =
new BytecodeBoundClass(
new ClassSymbol("java/util/List"),
() -> {
try {
return ByteStreams.toByteArray(
getClass().getClassLoader().getResourceAsStream("java/util/ArrayList.class"));
} catch (IOException e) {
throw new IOError(e);
}
},
null,
null);
try {
c.owner();
fail();
} catch (VerifyException e) {
assertThat(e)
.hasMessageThat()
.contains("expected class data for java/util/List, saw java/util/ArrayList instead");
}
}
@Test
public void nonJarFile() throws Exception {
Path lib = temporaryFolder.newFile("NOT_A_JAR").toPath();
MoreFiles.asCharSink(lib, UTF_8).write("hello");
try {
ClassPathBinder.bindClasspath(ImmutableList.of(lib));
fail();
} catch (IOException e) {
assertThat(e).hasMessageThat().contains("NOT_A_JAR");
}
}
@Test
public void resources() throws Exception {
Path path = temporaryFolder.newFile("tmp.jar").toPath();
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
jos.putNextEntry(new JarEntry("foo/bar/hello.txt"));
jos.write("hello".getBytes(UTF_8));
jos.putNextEntry(new JarEntry("foo/bar/Baz.class"));
jos.write("goodbye".getBytes(UTF_8));
}
ClassPath classPath = ClassPathBinder.bindClasspath(ImmutableList.of(path));
assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello");
assertThat(classPath.resource("foo/bar/Baz.class")).isNull();
}
@Test
public void resourcesFileManager() throws Exception {
Path path = temporaryFolder.newFile("tmp.jar").toPath();
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
jos.putNextEntry(new JarEntry("foo/bar/hello.txt"));
jos.write("hello".getBytes(UTF_8));
jos.putNextEntry(new JarEntry("foo/bar/Baz.class"));
jos.write("goodbye".getBytes(UTF_8));
}
StandardJavaFileManager fileManager =
ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8);
fileManager.setLocation(StandardLocation.CLASS_PATH, ImmutableList.of(path.toFile()));
ClassPath classPath = FileManagerClassBinder.adapt(fileManager, StandardLocation.CLASS_PATH);
assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello");
assertThat(classPath.resource("foo/bar/NoSuch.class")).isNull();
}
}