From cc1a628110bd05e08889eae810e17e7b6f546d5e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 14 Jan 2015 12:12:27 +0100 Subject: [PATCH 1/2] fix code coverage test to match the new implementation logic --- .../idea/coverage/KotlinCoverageExtension.kt | 145 +++++++++--------- .../outputFiles/NotInlinedLambda.expected.txt | 4 +- .../coverage/outputFiles/NotInlinedLambda.kt | 1 + .../org/demo/coverage/Foo$bar$1.class | Bin 0 -> 1306 bytes .../org/demo/coverage/Foo.class | Bin 0 -> 1838 bytes .../AbstractKotlinCoverageOutputFilesTest.kt | 13 +- 6 files changed, 87 insertions(+), 76 deletions(-) create mode 100644 idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class create mode 100644 idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo.class diff --git a/idea/src/org/jetbrains/kotlin/idea/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/kotlin/idea/coverage/KotlinCoverageExtension.kt index fe2d4b206bc..552a0643ae3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/coverage/KotlinCoverageExtension.kt +++ b/idea/src/org/jetbrains/kotlin/idea/coverage/KotlinCoverageExtension.kt @@ -50,7 +50,7 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { override fun suggestQualifiedName(sourceFile: PsiFile, classes: Array, names: MutableSet): Boolean { if (sourceFile is JetFile) { - val qNames = collectGeneratedClassQualifiedNames(sourceFile) + val qNames = collectGeneratedClassQualifiedNames(findOutputRoot(sourceFile), sourceFile) if (qNames != null) { names.addAll(qNames) return true @@ -67,78 +67,10 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { } LOG.info("Retrieving coverage for " + element.getName()) - val qualifiedNames = collectGeneratedClassQualifiedNames(element) + val qualifiedNames = collectGeneratedClassQualifiedNames(findOutputRoot(element), element) return if (qualifiedNames == null) null else totalCoverageForQualifiedNames(coverageAnnotator, qualifiedNames) } - private fun collectGeneratedClassQualifiedNames(file: JetFile): List? { - val outputRoot = findOutputRoot(file) - val existingClassFiles = getClassesGeneratedFromFile(outputRoot, file) - if (existingClassFiles.isEmpty()) { - return null - } - LOG.debug("Classfiles: [${existingClassFiles.map { it.getName() }.join()}]") - return existingClassFiles.map { - val relativePath = VfsUtilCore.getRelativePath(it, outputRoot) - StringUtil.trimEnd(relativePath, ".class").replace("/", ".") - } - } - - private fun getClassesGeneratedFromFile(outputRoot: VirtualFile?, file: JetFile): List { - val relativePath = file.getPackageFqName().asString().replace('.', '/') - val packageOutputDir = outputRoot?.findFileByRelativePath(relativePath) - if (packageOutputDir == null) return listOf() - - val prefixes = collectClassFilePrefixes(file) - LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]") - return packageOutputDir.getChildren().filter { - file -> prefixes.any { - (file.getName().startsWith(it + "$") && FileUtilRt.getExtension(file.getName()) == "class") || - file.getName() == it + ".class" - } - } - } - - private fun totalCoverageForQualifiedNames(coverageAnnotator: JavaCoverageAnnotator, - qualifiedNames: List): PackageAnnotator.ClassCoverageInfo { - val result = PackageAnnotator.ClassCoverageInfo() - result.totalClassCount = 0 - qualifiedNames.forEach { - val classInfo = coverageAnnotator.getClassCoverageInfo(it) - if (classInfo != null) { - result.totalClassCount += classInfo.totalClassCount - result.coveredClassCount += classInfo.coveredClassCount - result.totalMethodCount += classInfo.totalMethodCount - result.coveredMethodCount += classInfo.coveredMethodCount - result.totalLineCount += classInfo.totalLineCount - result.fullyCoveredLineCount += classInfo.fullyCoveredLineCount - result.partiallyCoveredLineCount += classInfo.partiallyCoveredLineCount - } - else { - LOG.debug("Found no coverage for ${it}") - } - } - return result - } - - private fun findOutputRoot(file: JetFile): VirtualFile? { - val module = ModuleUtilCore.findModuleForPsiElement(file) - if (module == null) return null - val fileIndex = ProjectRootManager.getInstance(file.getProject()).getFileIndex() - val inTests = fileIndex.isInTestSourceContent(file.getVirtualFile()) - val compilerOutputExtension = CompilerModuleExtension.getInstance(module) - return if (inTests) - compilerOutputExtension.getCompilerOutputPathForTests() - else - compilerOutputExtension.getCompilerOutputPath() - } - - private fun collectClassFilePrefixes(file: JetFile): Collection { - val result = file.getChildren().filter { it is JetClassOrObject }.map { (it as JetClassOrObject).getName() } - val packagePartFqName = PackagePartClassUtils.getPackagePartFqName(file) - return result.union(arrayListOf(packagePartFqName.shortName().asString())) - } - // Implements API added in IDEA 14.1 fun keepCoverageInfoForClassWithoutSource(bundle: CoverageSuitesBundle, classFile: File): Boolean { // TODO check scope and source roots @@ -177,10 +109,81 @@ public class KotlinCoverageExtension(): JavaCoverageEngineExtension() { runReadAction { val outputRoot = findOutputRoot(srcFile) val existingClassFiles = getClassesGeneratedFromFile(outputRoot, srcFile) - existingClassFiles.mapTo(classFiles) { File(it.getPath())} + existingClassFiles.mapTo(classFiles) { File(it.getPath()) } } return true } return false } + + class object { + private val LOG = Logger.getInstance(javaClass()) + + fun collectGeneratedClassQualifiedNames(outputRoot: VirtualFile?, file: JetFile): List? { + val existingClassFiles = getClassesGeneratedFromFile(outputRoot, file) + if (existingClassFiles.isEmpty()) { + return null + } + LOG.debug("Classfiles: [${existingClassFiles.map { it.getName() }.join()}]") + return existingClassFiles.map { + val relativePath = VfsUtilCore.getRelativePath(it, outputRoot) + StringUtil.trimEnd(relativePath, ".class").replace("/", ".") + } + } + + private fun totalCoverageForQualifiedNames(coverageAnnotator: JavaCoverageAnnotator, + qualifiedNames: List): PackageAnnotator.ClassCoverageInfo { + val result = PackageAnnotator.ClassCoverageInfo() + result.totalClassCount = 0 + qualifiedNames.forEach { + val classInfo = coverageAnnotator.getClassCoverageInfo(it) + if (classInfo != null) { + result.totalClassCount += classInfo.totalClassCount + result.coveredClassCount += classInfo.coveredClassCount + result.totalMethodCount += classInfo.totalMethodCount + result.coveredMethodCount += classInfo.coveredMethodCount + result.totalLineCount += classInfo.totalLineCount + result.fullyCoveredLineCount += classInfo.fullyCoveredLineCount + result.partiallyCoveredLineCount += classInfo.partiallyCoveredLineCount + } + else { + LOG.debug("Found no coverage for ${it}") + } + } + return result + } + + private fun getClassesGeneratedFromFile(outputRoot: VirtualFile?, file: JetFile): List { + val relativePath = file.getPackageFqName().asString().replace('.', '/') + val packageOutputDir = outputRoot?.findFileByRelativePath(relativePath) + if (packageOutputDir == null) return listOf() + + val prefixes = collectClassFilePrefixes(file) + LOG.debug("Classfile prefixes: [${prefixes.join(", ")}]") + return packageOutputDir.getChildren().filter { + file -> prefixes.any { + (file.getName().startsWith(it + "$") && FileUtilRt.getExtension(file.getName()) == "class") || + file.getName() == it + ".class" + } + } + } + + private fun findOutputRoot(file: JetFile): VirtualFile? { + val module = ModuleUtilCore.findModuleForPsiElement(file) + if (module == null) return null + val fileIndex = ProjectRootManager.getInstance(file.getProject()).getFileIndex() + val inTests = fileIndex.isInTestSourceContent(file.getVirtualFile()) + val compilerOutputExtension = CompilerModuleExtension.getInstance(module) + return if (inTests) + compilerOutputExtension.getCompilerOutputPathForTests() + else + compilerOutputExtension.getCompilerOutputPath() + } + + private fun collectClassFilePrefixes(file: JetFile): Collection { + val result = file.getChildren().filter { it is JetClassOrObject }.map { (it as JetClassOrObject).getName() } + val packagePartFqName = PackagePartClassUtils.getPackagePartFqName(file) + return result.union(arrayListOf(packagePartFqName.shortName().asString())) + } + } } diff --git a/idea/testData/coverage/outputFiles/NotInlinedLambda.expected.txt b/idea/testData/coverage/outputFiles/NotInlinedLambda.expected.txt index 39ea5e49821..f8aef9ad97a 100644 --- a/idea/testData/coverage/outputFiles/NotInlinedLambda.expected.txt +++ b/idea/testData/coverage/outputFiles/NotInlinedLambda.expected.txt @@ -1,2 +1,2 @@ -org/demo/coverage/Foo -org/demo/coverage/Foo$bar$1 +org.demo.coverage.Foo +org.demo.coverage.Foo$bar$1 diff --git a/idea/testData/coverage/outputFiles/NotInlinedLambda.kt b/idea/testData/coverage/outputFiles/NotInlinedLambda.kt index 3007ac1e940..56f67a65c1f 100644 --- a/idea/testData/coverage/outputFiles/NotInlinedLambda.kt +++ b/idea/testData/coverage/outputFiles/NotInlinedLambda.kt @@ -2,6 +2,7 @@ package org.demo.coverage public class Foo { public fun forEach(fn: (Any?) -> Unit): Unit { + fn(1) } public fun bar() { diff --git a/idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class b/idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class new file mode 100644 index 0000000000000000000000000000000000000000..175d65c27510cee327bc961d03ba48ad1a5dca84 GIT binary patch literal 1306 zcma)5X>SuT6db2%(xic2T;)DOY0I&c8ybqLib{d7MWl_yC**8Q>SkkCyPHV-D-H<| zNc;eP6k;5bLPa?~?Am@ie)HbU+ppg~eF1O{4+M_csOdFS+j@cRsz^4Kw`y&%E~DZ? z7DAv=Y1t&y#@p()J#7*d85w%39TOzlnsR$PEDaDFMkk9)i-Y3;vrwvR$*%N5X`0?z zeM<$&4yJ!a;Pk&(vPcVz_fb2kv&aZc`8FvV)~Fg4*{(O_l~y8<(WYx#N?>O8cmBEG zLjni)0@e!1VKk2v3Id~jephxD`vr1kzq-EcuRJUY>>D)RYpQ^8Oyn?zNde|ni5GQD zqT9+#UTrIFURv*wN@_CfsHZZLZAE1l1=5CFfs|$kX@?XLw5eEeJ~2x@lG?< zIS_k(Ck$mhRJ=X((%KOmr6m1Awe`yOsSH{)HjCo|+3koXhNgg%uB#I`<#;xAEO4U7 z@rPMp^2xn4%ak;=SR(c89HlDad>+G?5f~{2bcCOrbTdwkvpMeO1?C4j?V6~^y(z1{ zGRdY&bg&Z2I3{Uc)^$xqF*_aN%ucb|mNpFm@4vGYAM=8%f7xI2pFdgKsJ>X;@K@H$ zYd$^SbN;iq#*X+C6mSFQ$+==1bYiT+8S^{sx{B7F;!YDA$gn0O?auw+*=IkTuIX4a zx?}J1Pt)jVA5=Eozrk`<-83@kL?p{sZ6^xUs&>p{4^0r-Sexb(m26rnGQFGj7dBj_ z^24C}e5RchlroKFPIjJ=TRfd1BE>Z~fAKx`y~WgP9t3Xlo#6@$VZ!|gTpj!#V-B8~ zJoXW%-*A`09lj6!h%%j{aCZ;k+^#%%++}=%V~qm@AB>4O9ODR60$CTHb{E|waqqY$ zxVj34=Re?rqr&(IW_Lax?ok4xpOJe00dwzgu?J#8xf)|j&LyT9+RVp>Wf-A6VmW8WS zw$suYM%UKr_P}s> z%g~N&TLwg!yW|6|b-2~iPHLA%-IYN?SZ>>H$F#J|L02;^*KjP}(T*noFT#A;b_*6k zj7EufYYo2Bb`fR{>a=CLhX_(4d5#c_kBD*|-s>SGN@HHfXm-fgTl_;*p?lTIAjnPI z$@BUJ1wTD?)VJ!cX}!6HKV<^=gMT81M-j8ZWW_c>`IEF=oa zTO?ypCJ4*0;)f-;PxTx}JE&ELj&T2O4-H1{z)LV5qksq(Sk62X+zENjPtccPQwg!YDdFubJnD)1#_O^hl_Zo(6Y0V!Rp+ zrTf;3&+|j^V3V;&&6kH~So?PG)p0TSYJ17&k@ic8Z1(Y$xm#+LCC}69e(BlCoX?Z@ z-Ak}+%C4TC{iuJp*9-Vo%}sxY6V#=>o# zd1oFfI_it}X}_pm%#fdCrIwRec($OcIf0An5#8&VSCV1Ov5Jh!x=Q}#{Bi}?B)=5K z${P7?Zzoljbub+o***DXijxUDb&b^f-m#FSXc*6Vy-lq~h9^Rn!_*%+cK3K#353jT zDUJ%0oRseW`a=*haNvi1cp3yh1QZg(7gWQX-5`xeN_L%h&bedqTE{pO1Hx>HdR4jK ztr^aEN2!`Ei@SY?tb6bFn1k(GFKzrRQj_?;p(d9(9JZmg(@_UHH+v>|n)U+yrmrBx z$Do2WcpuDPwfj!pI5I`Vu{Vit@sZdWX`R#$I2RKMAPsw9hJFg|>a>c|EdG=TyN1Kx z;LBfz%dmJl1Qn1XkuaJgsYv7+K1_M8Vf9alGk77e46-yXj1ce;HfTjyn1fBC&e6Ac zfj;m;n8vSY%^(j)GzXCYAJG47kI>I(M;1F_`Upc}8sQTof}*@75piND$yK@DYJ`Ou*o4vM`XLK=V9dKBd)X1$Ydf JzXa^T-hb;_v)}*# literal 0 HcmV?d00001 diff --git a/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt b/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt index 478f8f4ee85..1e61f26a57c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt @@ -18,6 +18,11 @@ package org.jetbrains.kotlin.idea.coverage import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.PluginTestCaseBase +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.test.JetTestUtils +import java.io.File +import com.intellij.openapi.vfs.LocalFileSystem +import kotlin.test.assertNotNull public abstract class AbstractKotlinCoverageOutputFilesTest(): JetLightCodeInsightFixtureTestCase() { private val TEST_DATA_PATH = PluginTestCaseBase.getTestDataPathBase() + "/coverage/outputFiles" @@ -25,8 +30,10 @@ public abstract class AbstractKotlinCoverageOutputFilesTest(): JetLightCodeInsig override fun getTestDataPath(): String? = TEST_DATA_PATH public fun doTest(path: String) { -// val kotlinFile = myFixture.configureByFile(path) as JetFile -// val actualClasses = KotlinCoverageExtension.collectOutputClassNames(kotlinFile) -// JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.txt")), actualClasses.join("\n")) + val kotlinFile = myFixture.configureByFile(path) as JetFile + val outputRoot = LocalFileSystem.getInstance().findFileByPath("$path.out") + assertNotNull(outputRoot) + val actualClasses = KotlinCoverageExtension.collectGeneratedClassQualifiedNames(outputRoot, kotlinFile) + JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.txt")), actualClasses!!.join("\n")) } } From 548b52d529435b1c5874e286b5de8ddf41d0fc8a Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 14 Jan 2015 14:49:07 +0100 Subject: [PATCH 2/2] don't store .class files in testdata --- .../outputFiles/NotInlinedLambda.classes.txt | 2 + .../org/demo/coverage/Foo$bar$1.class | Bin 1306 -> 0 bytes .../org/demo/coverage/Foo.class | Bin 1838 -> 0 bytes .../AbstractKotlinCoverageOutputFilesTest.kt | 37 +++++++++++++++--- 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 idea/testData/coverage/outputFiles/NotInlinedLambda.classes.txt delete mode 100644 idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class delete mode 100644 idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo.class diff --git a/idea/testData/coverage/outputFiles/NotInlinedLambda.classes.txt b/idea/testData/coverage/outputFiles/NotInlinedLambda.classes.txt new file mode 100644 index 00000000000..8b489ae9deb --- /dev/null +++ b/idea/testData/coverage/outputFiles/NotInlinedLambda.classes.txt @@ -0,0 +1,2 @@ +org/demo/coverage/Foo.class +org/demo/coverage/Foo$bar$1.class diff --git a/idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class b/idea/testData/coverage/outputFiles/NotInlinedLambda.kt.out/org/demo/coverage/Foo$bar$1.class deleted file mode 100644 index 175d65c27510cee327bc961d03ba48ad1a5dca84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1306 zcma)5X>SuT6db2%(xic2T;)DOY0I&c8ybqLib{d7MWl_yC**8Q>SkkCyPHV-D-H<| zNc;eP6k;5bLPa?~?Am@ie)HbU+ppg~eF1O{4+M_csOdFS+j@cRsz^4Kw`y&%E~DZ? z7DAv=Y1t&y#@p()J#7*d85w%39TOzlnsR$PEDaDFMkk9)i-Y3;vrwvR$*%N5X`0?z zeM<$&4yJ!a;Pk&(vPcVz_fb2kv&aZc`8FvV)~Fg4*{(O_l~y8<(WYx#N?>O8cmBEG zLjni)0@e!1VKk2v3Id~jephxD`vr1kzq-EcuRJUY>>D)RYpQ^8Oyn?zNde|ni5GQD zqT9+#UTrIFURv*wN@_CfsHZZLZAE1l1=5CFfs|$kX@?XLw5eEeJ~2x@lG?< zIS_k(Ck$mhRJ=X((%KOmr6m1Awe`yOsSH{)HjCo|+3koXhNgg%uB#I`<#;xAEO4U7 z@rPMp^2xn4%ak;=SR(c89HlDad>+G?5f~{2bcCOrbTdwkvpMeO1?C4j?V6~^y(z1{ zGRdY&bg&Z2I3{Uc)^$xqF*_aN%ucb|mNpFm@4vGYAM=8%f7xI2pFdgKsJ>X;@K@H$ zYd$^SbN;iq#*X+C6mSFQ$+==1bYiT+8S^{sx{B7F;!YDA$gn0O?auw+*=IkTuIX4a zx?}J1Pt)jVA5=Eozrk`<-83@kL?p{sZ6^xUs&>p{4^0r-Sexb(m26rnGQFGj7dBj_ z^24C}e5RchlroKFPIjJ=TRfd1BE>Z~fAKx`y~WgP9t3Xlo#6@$VZ!|gTpj!#V-B8~ zJoXW%-*A`09lj6!h%%j{aCZ;k+^#%%++}=%V~qm@AB>4O9ODR60$CTHb{E|waqqY$ zxVj34=Re?rqr&(IW_Lax?ok4xpOJe00dwzgu?J#8xf)|j&LyT9+RVp>Wf-A6VmW8WS zw$suYM%UKr_P}s> z%g~N&TLwg!yW|6|b-2~iPHLA%-IYN?SZ>>H$F#J|L02;^*KjP}(T*noFT#A;b_*6k zj7EufYYo2Bb`fR{>a=CLhX_(4d5#c_kBD*|-s>SGN@HHfXm-fgTl_;*p?lTIAjnPI z$@BUJ1wTD?)VJ!cX}!6HKV<^=gMT81M-j8ZWW_c>`IEF=oa zTO?ypCJ4*0;)f-;PxTx}JE&ELj&T2O4-H1{z)LV5qksq(Sk62X+zENjPtccPQwg!YDdFubJnD)1#_O^hl_Zo(6Y0V!Rp+ zrTf;3&+|j^V3V;&&6kH~So?PG)p0TSYJ17&k@ic8Z1(Y$xm#+LCC}69e(BlCoX?Z@ z-Ak}+%C4TC{iuJp*9-Vo%}sxY6V#=>o# zd1oFfI_it}X}_pm%#fdCrIwRec($OcIf0An5#8&VSCV1Ov5Jh!x=Q}#{Bi}?B)=5K z${P7?Zzoljbub+o***DXijxUDb&b^f-m#FSXc*6Vy-lq~h9^Rn!_*%+cK3K#353jT zDUJ%0oRseW`a=*haNvi1cp3yh1QZg(7gWQX-5`xeN_L%h&bedqTE{pO1Hx>HdR4jK ztr^aEN2!`Ei@SY?tb6bFn1k(GFKzrRQj_?;p(d9(9JZmg(@_UHH+v>|n)U+yrmrBx z$Do2WcpuDPwfj!pI5I`Vu{Vit@sZdWX`R#$I2RKMAPsw9hJFg|>a>c|EdG=TyN1Kx z;LBfz%dmJl1Qn1XkuaJgsYv7+K1_M8Vf9alGk77e46-yXj1ce;HfTjyn1fBC&e6Ac zfj;m;n8vSY%^(j)GzXCYAJG47kI>I(M;1F_`Upc}8sQTof}*@75piND$yK@DYJ`Ou*o4vM`XLK=V9dKBd)X1$Ydf JzXa^T-hb;_v)}*# diff --git a/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt b/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt index 1e61f26a57c..fbadb9ec262 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/coverage/AbstractKotlinCoverageOutputFilesTest.kt @@ -21,8 +21,9 @@ import org.jetbrains.kotlin.idea.PluginTestCaseBase import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.test.JetTestUtils import java.io.File -import com.intellij.openapi.vfs.LocalFileSystem -import kotlin.test.assertNotNull +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.util.application.runWriteAction public abstract class AbstractKotlinCoverageOutputFilesTest(): JetLightCodeInsightFixtureTestCase() { private val TEST_DATA_PATH = PluginTestCaseBase.getTestDataPathBase() + "/coverage/outputFiles" @@ -31,9 +32,33 @@ public abstract class AbstractKotlinCoverageOutputFilesTest(): JetLightCodeInsig public fun doTest(path: String) { val kotlinFile = myFixture.configureByFile(path) as JetFile - val outputRoot = LocalFileSystem.getInstance().findFileByPath("$path.out") - assertNotNull(outputRoot) - val actualClasses = KotlinCoverageExtension.collectGeneratedClassQualifiedNames(outputRoot, kotlinFile) - JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.txt")), actualClasses!!.join("\n")) + val outDir = myFixture.getTempDirFixture().findOrCreateDir("coverageTestOut") + try { + FileUtil.loadLines(File(path.replace(".kt", ".classes.txt"))).forEach { + runWriteAction { + createEmptyFile(outDir, it) + } + } + + val actualClasses = KotlinCoverageExtension.collectGeneratedClassQualifiedNames(outDir, kotlinFile) + JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.txt")), actualClasses!!.join("\n")) + } + finally { + runWriteAction { + outDir.delete(null) + } + } + } +} + +private fun createEmptyFile(dir: VirtualFile, relativePath: String) { + var currentDir = dir + val segments = relativePath.split('/') + segments.forEachIndexed {(i, s) -> + if (i < segments.size() - 1) { + currentDir = currentDir.createChildDirectory(null, s) + } else { + currentDir.createChildData(null, s) + } } }