0b26e749f6
Instead of inheriting from KotlinMultiFileTestWithJava, invoke KotlinTestUtils.createTestFiles directly. This helps to avoid constructing unnecessary environment (createEnvironment() was called in setUp, but codegen tests ignored the created environment because it's not possible to do generically in codegen tests; the environment kind depends on the source file usually)
421 lines
15 KiB
Java
421 lines
15 KiB
Java
/*
|
|
* Copyright 2010-2015 JetBrains s.r.o.
|
|
*
|
|
* 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 org.jetbrains.kotlin.codegen;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.intellij.openapi.util.Ref;
|
|
import com.intellij.openapi.util.io.FileUtil;
|
|
import com.intellij.testFramework.TestDataFile;
|
|
import com.intellij.testFramework.UsefulTestCase;
|
|
import kotlin.collections.ArraysKt;
|
|
import kotlin.io.FilesKt;
|
|
import kotlin.text.Charsets;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.jetbrains.kotlin.backend.common.output.OutputFile;
|
|
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
|
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
|
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
|
|
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
|
|
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
|
|
import org.jetbrains.kotlin.config.CompilerConfiguration;
|
|
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
|
|
import org.jetbrains.kotlin.name.FqName;
|
|
import org.jetbrains.kotlin.psi.KtFile;
|
|
import org.jetbrains.kotlin.test.ConfigurationKind;
|
|
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
|
import org.jetbrains.kotlin.test.TestJdkKind;
|
|
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
|
|
import org.jetbrains.org.objectweb.asm.ClassReader;
|
|
import org.jetbrains.org.objectweb.asm.tree.ClassNode;
|
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer;
|
|
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
|
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
|
|
import org.jetbrains.org.objectweb.asm.tree.analysis.SimpleVerifier;
|
|
import org.jetbrains.org.objectweb.asm.util.Textifier;
|
|
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.reflect.Method;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static org.jetbrains.kotlin.codegen.CodegenTestUtil.*;
|
|
import static org.jetbrains.kotlin.test.KotlinTestUtils.compilerConfigurationForTests;
|
|
import static org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar;
|
|
|
|
public abstract class CodegenTestCase extends UsefulTestCase {
|
|
private static final String DEFAULT_TEST_FILE_NAME = "a_test";
|
|
|
|
protected KotlinCoreEnvironment myEnvironment;
|
|
protected CodegenTestFiles myFiles;
|
|
protected ClassFileFactory classFileFactory;
|
|
protected GeneratedClassLoader initializedClassLoader;
|
|
protected ConfigurationKind configurationKind;
|
|
|
|
protected final void createEnvironmentWithMockJdkAndIdeaAnnotations(
|
|
@NotNull ConfigurationKind configurationKind,
|
|
@Nullable File... javaSourceRoots
|
|
) {
|
|
if (myEnvironment != null) {
|
|
throw new IllegalStateException("must not set up myEnvironment twice");
|
|
}
|
|
|
|
CompilerConfiguration configuration = compilerConfigurationForTests(
|
|
configurationKind, TestJdkKind.MOCK_JDK, Collections.singletonList(getAnnotationsJar()),
|
|
ArraysKt.filterNotNull(javaSourceRoots)
|
|
);
|
|
|
|
myEnvironment = KotlinCoreEnvironment.createForTests(
|
|
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
|
);
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception {
|
|
myFiles = null;
|
|
myEnvironment = null;
|
|
classFileFactory = null;
|
|
|
|
if (initializedClassLoader != null) {
|
|
initializedClassLoader.dispose();
|
|
initializedClassLoader = null;
|
|
}
|
|
|
|
super.tearDown();
|
|
}
|
|
|
|
protected void loadText(@NotNull String text) {
|
|
myFiles = CodegenTestFiles.create(DEFAULT_TEST_FILE_NAME + ".kt", text, myEnvironment.getProject());
|
|
}
|
|
|
|
@NotNull
|
|
protected String loadFile(@NotNull @TestDataFile String name) {
|
|
return loadFileByFullPath(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + name);
|
|
}
|
|
|
|
@NotNull
|
|
protected String loadFileByFullPath(@NotNull String fullPath) {
|
|
try {
|
|
File file = new File(fullPath);
|
|
String content = FileUtil.loadFile(file, Charsets.UTF_8.name(), true);
|
|
myFiles = CodegenTestFiles.create(file.getName(), content, myEnvironment.getProject());
|
|
return content;
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
protected void loadFiles(@NotNull String... names) {
|
|
myFiles = CodegenTestFiles.create(myEnvironment.getProject(), names);
|
|
}
|
|
|
|
protected void loadFile() {
|
|
loadFile(getPrefix() + "/" + getTestName(true) + ".kt");
|
|
}
|
|
|
|
protected void loadMultiFiles(@NotNull List<TestFile> files) {
|
|
Collections.sort(files);
|
|
|
|
List<KtFile> ktFiles = new ArrayList<KtFile>(files.size());
|
|
for (TestFile file : files) {
|
|
if (file.name.endsWith(".kt")) {
|
|
String content = CheckerTestUtil.parseDiagnosedRanges(file.content, new ArrayList<CheckerTestUtil.DiagnosedRange>(0));
|
|
ktFiles.add(KotlinTestUtils.createFile(file.name, content, myEnvironment.getProject()));
|
|
}
|
|
}
|
|
|
|
myFiles = CodegenTestFiles.create(ktFiles);
|
|
}
|
|
|
|
@NotNull
|
|
protected String codegenTestBasePath() {
|
|
return "compiler/testData/codegen/";
|
|
}
|
|
|
|
@NotNull
|
|
protected String relativePath(@NotNull File file) {
|
|
String stringToCut = codegenTestBasePath();
|
|
String systemIndependentPath = file.getPath().replace(File.separatorChar, '/');
|
|
assert systemIndependentPath.startsWith(stringToCut) : "File path is not absolute: " + file;
|
|
return systemIndependentPath.substring(stringToCut.length());
|
|
}
|
|
|
|
@NotNull
|
|
protected String getPrefix() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@NotNull
|
|
protected GeneratedClassLoader generateAndCreateClassLoader() {
|
|
if (initializedClassLoader != null) {
|
|
fail("Double initialization of class loader in same test");
|
|
}
|
|
|
|
initializedClassLoader = createClassLoader();
|
|
|
|
if (!verifyAllFilesWithAsm(generateClassesInFile(), initializedClassLoader)) {
|
|
fail("Verification failed: see exceptions above");
|
|
}
|
|
|
|
return initializedClassLoader;
|
|
}
|
|
|
|
@NotNull
|
|
protected GeneratedClassLoader createClassLoader() {
|
|
return new GeneratedClassLoader(
|
|
generateClassesInFile(),
|
|
configurationKind == ConfigurationKind.NO_KOTLIN_REFLECT ?
|
|
ForTestCompileRuntime.runtimeJarClassLoader() :
|
|
ForTestCompileRuntime.runtimeAndReflectJarClassLoader(),
|
|
getClassPathURLs()
|
|
);
|
|
}
|
|
|
|
@NotNull
|
|
private URL[] getClassPathURLs() {
|
|
List<URL> urls = Lists.newArrayList();
|
|
for (File file : JvmContentRootsKt.getJvmClasspathRoots(myEnvironment.getConfiguration())) {
|
|
try {
|
|
urls.add(file.toURI().toURL());
|
|
}
|
|
catch (MalformedURLException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
return urls.toArray(new URL[urls.size()]);
|
|
}
|
|
|
|
@NotNull
|
|
protected String generateToText() {
|
|
if (classFileFactory == null) {
|
|
classFileFactory = generateFiles(myEnvironment, myFiles);
|
|
}
|
|
return classFileFactory.createText();
|
|
}
|
|
|
|
@NotNull
|
|
protected Map<String, String> generateEachFileToText() {
|
|
if (classFileFactory == null) {
|
|
classFileFactory = generateFiles(myEnvironment, myFiles);
|
|
}
|
|
return classFileFactory.createTextForEachFile();
|
|
}
|
|
|
|
@NotNull
|
|
protected Class<?> generateFacadeClass() {
|
|
FqName facadeClassFqName = JvmFileClassUtil.getFileClassInfoNoResolve(myFiles.getPsiFile()).getFacadeClassFqName();
|
|
return generateClass(facadeClassFqName.asString());
|
|
}
|
|
|
|
@NotNull
|
|
protected Class<?> generateFileClass() {
|
|
FqName fileClassFqName = JvmFileClassUtil.getFileClassInfoNoResolve(myFiles.getPsiFile()).getFileClassFqName();
|
|
return generateClass(fileClassFqName.asString());
|
|
}
|
|
|
|
@NotNull
|
|
protected Class<?> generateClass(@NotNull String name) {
|
|
try {
|
|
return generateAndCreateClassLoader().loadClass(name);
|
|
}
|
|
catch (ClassNotFoundException e) {
|
|
fail("No class file was generated for: " + name);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
protected ClassFileFactory generateClassesInFile() {
|
|
if (classFileFactory == null) {
|
|
try {
|
|
classFileFactory = generateFiles(myEnvironment, myFiles);
|
|
|
|
if (DxChecker.RUN_DX_CHECKER) {
|
|
DxChecker.check(classFileFactory);
|
|
}
|
|
}
|
|
catch (Throwable e) {
|
|
e.printStackTrace();
|
|
System.err.println("Generating instructions as text...");
|
|
try {
|
|
if (classFileFactory == null) {
|
|
System.out.println("Cannot generate text: exception was thrown during generation");
|
|
}
|
|
else {
|
|
System.out.println(classFileFactory.createText());
|
|
}
|
|
}
|
|
catch (Throwable e1) {
|
|
System.err.println("Exception thrown while trying to generate text, the actual exception follows:");
|
|
e1.printStackTrace();
|
|
System.err.println("-----------------------------------------------------------------------------");
|
|
}
|
|
fail("See exceptions above");
|
|
}
|
|
}
|
|
return classFileFactory;
|
|
}
|
|
|
|
private static boolean verifyAllFilesWithAsm(ClassFileFactory factory, ClassLoader loader) {
|
|
boolean noErrors = true;
|
|
for (OutputFile file : ClassFileUtilsKt.getClassFiles(factory)) {
|
|
noErrors &= verifyWithAsm(file, loader);
|
|
}
|
|
return noErrors;
|
|
}
|
|
|
|
private static boolean verifyWithAsm(@NotNull OutputFile file, ClassLoader loader) {
|
|
ClassNode classNode = new ClassNode();
|
|
new ClassReader(file.asByteArray()).accept(classNode, 0);
|
|
|
|
SimpleVerifier verifier = new SimpleVerifier();
|
|
verifier.setClassLoader(loader);
|
|
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(verifier);
|
|
|
|
boolean noErrors = true;
|
|
for (MethodNode method : classNode.methods) {
|
|
try {
|
|
analyzer.analyze(classNode.name, method);
|
|
}
|
|
catch (Throwable e) {
|
|
System.err.println(file.asText());
|
|
System.err.println(classNode.name + "::" + method.name + method.desc);
|
|
|
|
//noinspection InstanceofCatchParameter
|
|
if (e instanceof AnalyzerException) {
|
|
// Print the erroneous instruction
|
|
TraceMethodVisitor tmv = new TraceMethodVisitor(new Textifier());
|
|
((AnalyzerException) e).node.accept(tmv);
|
|
PrintWriter pw = new PrintWriter(System.err);
|
|
tmv.p.print(pw);
|
|
pw.flush();
|
|
}
|
|
|
|
e.printStackTrace();
|
|
noErrors = false;
|
|
}
|
|
}
|
|
return noErrors;
|
|
}
|
|
|
|
@NotNull
|
|
protected Method generateFunction() {
|
|
Class<?> aClass = generateFacadeClass();
|
|
try {
|
|
return findTheOnlyMethod(aClass);
|
|
} catch (Error e) {
|
|
System.out.println(generateToText());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
protected Method generateFunction(@NotNull String name) {
|
|
return findDeclaredMethodByName(generateFacadeClass(), name);
|
|
}
|
|
|
|
@NotNull
|
|
public Class<? extends Annotation> loadAnnotationClassQuietly(@NotNull String fqName) {
|
|
try {
|
|
//noinspection unchecked
|
|
return (Class<? extends Annotation>) initializedClassLoader.loadClass(fqName);
|
|
}
|
|
catch (ClassNotFoundException e) {
|
|
throw ExceptionUtilsKt.rethrow(e);
|
|
}
|
|
}
|
|
|
|
public static class TestFile implements Comparable<TestFile> {
|
|
public final String name;
|
|
public final String content;
|
|
|
|
public TestFile(@NotNull String name, @NotNull String content) {
|
|
this.name = name;
|
|
this.content = content;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(@NotNull TestFile o) {
|
|
return name.compareTo(o.name);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return name.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return obj instanceof TestFile && ((TestFile) obj).name.equals(name);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
protected void doTest(String filePath) throws Exception {
|
|
File file = new File(filePath);
|
|
String expectedText = KotlinTestUtils.doLoadFile(file);
|
|
final Ref<File> javaFilesDir = Ref.create();
|
|
|
|
List<TestFile> testFiles =
|
|
KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactoryNoModules<TestFile>() {
|
|
@NotNull
|
|
@Override
|
|
public TestFile create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
|
|
if (fileName.endsWith(".java")) {
|
|
if (javaFilesDir.isNull()) {
|
|
try {
|
|
javaFilesDir.set(KotlinTestUtils.tmpDir("java-files"));
|
|
}
|
|
catch (IOException e) {
|
|
throw ExceptionUtilsKt.rethrow(e);
|
|
}
|
|
}
|
|
writeSourceFile(fileName, text, javaFilesDir.get());
|
|
}
|
|
|
|
return new TestFile(fileName, text);
|
|
}
|
|
|
|
private void writeSourceFile(@NotNull String fileName, @NotNull String content, @NotNull File targetDir) {
|
|
File file = new File(targetDir, fileName);
|
|
KotlinTestUtils.mkdirs(file.getParentFile());
|
|
FilesKt.writeText(file, content, Charsets.UTF_8);
|
|
}
|
|
});
|
|
|
|
doMultiFileTest(file, testFiles, javaFilesDir.get());
|
|
}
|
|
|
|
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
|
|
throw new UnsupportedOperationException("Multi-file test cases are not supported in this test");
|
|
}
|
|
}
|