Files
kotlin-fork/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java
T
Alexander Udalov 0b26e749f6 Improve multi-file test framework in CodegenTestCase
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)
2016-03-02 15:44:28 +03:00

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