/* * 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.google.common.collect.Maps; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import kotlin.Pair; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.ConfigurationKind; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.TestCaseWithTmpdir; import org.jetbrains.kotlin.test.TestJdkKind; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; import org.jetbrains.org.objectweb.asm.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public abstract class AbstractLineNumberTest extends TestCaseWithTmpdir { private static final String LINE_NUMBER_FUN = "lineNumber"; private static final Pattern TEST_LINE_NUMBER_PATTERN = Pattern.compile("^.*test." + LINE_NUMBER_FUN + "\\(\\).*$"); @NotNull private KotlinCoreEnvironment createEnvironment() { return KotlinCoreEnvironment.createForTests( myTestRootDisposable, KotlinTestUtils.newConfiguration( ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, KotlinTestUtils.getAnnotationsJar(), tmpdir ), EnvironmentConfigFiles.JVM_CONFIG_FILES ); } @Override public void setUp() throws Exception { super.setUp(); KotlinCoreEnvironment environment = createEnvironment(); KtFile psiFile = KotlinTestUtils.createFile( LINE_NUMBER_FUN + ".kt", "package test;\n\npublic fun " + LINE_NUMBER_FUN + "(): Int = 0\n", environment.getProject() ); GenerationUtils.compileFileTo(psiFile, environment, tmpdir); } @NotNull private Pair createPsiFile(@NotNull String filename) { File file = new File(filename); KotlinCoreEnvironment environment = createEnvironment(); String text; try { text = FileUtil.loadFile(file, true); } catch (IOException e) { throw ExceptionUtilsKt.rethrow(e); } return new Pair( KotlinTestUtils.createFile(file.getName(), text, environment.getProject()), environment ); } private void doTest(@NotNull String filename, boolean custom) { Pair fileAndEnv = createPsiFile(filename); KtFile psiFile = fileAndEnv.getFirst(); KotlinCoreEnvironment environment = fileAndEnv.getSecond(); ClassFileFactory classFileFactory = GenerationUtils.compileFile(psiFile, environment); if (custom) { List actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, false); String text = psiFile.getText(); String newFileText = text.substring(0, text.indexOf("// ")) + getActualLineNumbersAsString(actualLineNumbers); KotlinTestUtils.assertEqualsToFile(new File(filename), newFileText); } else { List expectedLineNumbers = extractSelectedLineNumbersFromSource(psiFile); List actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, true); assertSameElements(actualLineNumbers, expectedLineNumbers); } } private static String getActualLineNumbersAsString(List list) { return CollectionsKt.joinToString(list, " ", "// ", "", -1, "...", new Function1() { @Override public CharSequence invoke(Integer integer) { return integer.toString(); } }); } @NotNull private static List extractActualLineNumbersFromBytecode(@NotNull ClassFileFactory factory, boolean testFunInvoke) { List actualLineNumbers = Lists.newArrayList(); for (OutputFile outputFile : ClassFileUtilsKt.getClassFiles(factory)) { ClassReader cr = new ClassReader(outputFile.asByteArray()); try { List lineNumbers = testFunInvoke ? readTestFunLineNumbers(cr) : readAllLineNumbers(cr); actualLineNumbers.addAll(lineNumbers); } catch (Throwable e) { System.out.println(factory.createText()); throw ExceptionUtilsKt.rethrow(e); } } return actualLineNumbers; } protected void doTest(String path) { doTest(path, false); } protected void doTestCustom(String path) { doTest(path, true); } @NotNull private static List extractSelectedLineNumbersFromSource(@NotNull KtFile file) { String fileContent = file.getText(); List lineNumbers = Lists.newArrayList(); String[] lines = StringUtil.convertLineSeparators(fileContent).split("\n"); for (int i = 0; i < lines.length; i++) { Matcher matcher = TEST_LINE_NUMBER_PATTERN.matcher(lines[i]); if (matcher.matches()) { lineNumbers.add(i + 1); } } return lineNumbers; } @NotNull private static List readTestFunLineNumbers(@NotNull ClassReader cr) { final List