blob: 26608505329f6fd7dc83764b64b7eb216a8b65f9 [file] [log] [blame]
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.jps.build
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.testFramework.LightVirtualFile
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.io.Decompressor
import com.intellij.util.io.URLUtil
import com.intellij.util.io.ZipUtil
import org.jetbrains.jps.ModuleChunk
import org.jetbrains.jps.api.CanceledStatus
import org.jetbrains.jps.builders.BuildResult
import org.jetbrains.jps.builders.CompileScopeTestBuilder
import org.jetbrains.jps.builders.TestProjectBuilderLogger
import org.jetbrains.jps.builders.impl.BuildDataPathsImpl
import org.jetbrains.jps.builders.logging.BuildLoggingManager
import org.jetbrains.jps.cmdline.ProjectDescriptor
import org.jetbrains.jps.devkit.model.JpsPluginModuleType
import org.jetbrains.jps.incremental.BuilderRegistry
import org.jetbrains.jps.incremental.CompileContext
import org.jetbrains.jps.incremental.IncProjectBuilder
import org.jetbrains.jps.incremental.ModuleLevelBuilder
import org.jetbrains.jps.incremental.messages.BuildMessage
import org.jetbrains.jps.incremental.messages.CompilerMessage
import org.jetbrains.jps.model.JpsModuleRootModificationUtil
import org.jetbrains.jps.model.JpsProject
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.jps.model.java.JpsJavaDependencyScope
import org.jetbrains.jps.model.java.JpsJavaExtensionService
import org.jetbrains.jps.model.java.JpsJavaSdkType
import org.jetbrains.jps.model.library.JpsOrderRootType
import org.jetbrains.jps.model.module.JpsModule
import org.jetbrains.jps.util.JpsPathUtil
import org.jetbrains.kotlin.cli.common.Usage
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.config.KotlinFacetSettings
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTestBase.LibraryDependency.*
import org.jetbrains.kotlin.jps.incremental.CacheAttributesDiff
import org.jetbrains.kotlin.jps.model.JpsKotlinFacetModuleExtension
import org.jetbrains.kotlin.jps.model.kotlinCommonCompilerArguments
import org.jetbrains.kotlin.jps.model.kotlinCompilerArguments
import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtilExt
import org.jetbrains.kotlin.test.kotlinPathsForDistDirectoryForTests
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.zip.ZipOutputStream
open class KotlinJpsBuildTest : KotlinJpsBuildTestBase() {
companion object {
private const val ADDITIONAL_MODULE_NAME = "module2"
private val EXCLUDE_FILES = arrayOf("Excluded.class", "YetAnotherExcluded.class")
private val NOTHING = arrayOf<String>()
private const val KOTLIN_JS_LIBRARY = "jslib-example"
private val PATH_TO_KOTLIN_JS_LIBRARY = AbstractKotlinJpsBuildTestCase.TEST_DATA_PATH + "general/KotlinJavaScriptProjectWithDirectoryAsLibrary/" + KOTLIN_JS_LIBRARY
private const val KOTLIN_JS_LIBRARY_JAR = "$KOTLIN_JS_LIBRARY.jar"
private fun getMethodsOfClass(classFile: File): Set<String> {
val result = TreeSet<String>()
ClassReader(FileUtil.loadFileBytes(classFile)).accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
result.add(name)
return null
}
}, 0)
return result
}
@JvmStatic
protected fun klass(moduleName: String, classFqName: String): String {
val outputDirPrefix = "out/production/$moduleName/"
return outputDirPrefix + classFqName.replace('.', '/') + ".class"
}
@JvmStatic
protected fun module(moduleName: String): String {
return "out/production/$moduleName/${JvmCodegenUtil.getMappingFileName(moduleName)}"
}
}
protected fun doTest() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
}
protected fun doTestWithRuntime() {
initProject(JVM_FULL_RUNTIME)
buildAllModules().assertSuccessful()
}
protected fun doTestWithKotlinJavaScriptLibrary() {
initProject(JS_STDLIB)
createKotlinJavaScriptLibraryArchive()
addDependency(KOTLIN_JS_LIBRARY, File(workDir, KOTLIN_JS_LIBRARY_JAR))
buildAllModules().assertSuccessful()
}
fun testKotlinProject() {
doTest()
checkWhen(touch("src/test1.kt"), null, packageClasses("kotlinProject", "src/test1.kt", "Test1Kt"))
}
fun testSourcePackagePrefix() {
doTest()
}
fun testSourcePackageLongPrefix() {
initProject(JVM_MOCK_RUNTIME)
val buildResult = buildAllModules()
buildResult.assertSuccessful()
val warnings = buildResult.getMessages(BuildMessage.Kind.WARNING)
assertEquals("Warning about invalid package prefix in module 2 is expected: $warnings", 1, warnings.size)
assertEquals("Invalid package prefix name is ignored: invalid-prefix.test", warnings.first().messageText)
}
fun testSourcePackagePrefixWithInnerClasses() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptProject() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, pathsToDelete = k2jsOutput(PROJECT_NAME))
}
private fun k2jsOutput(vararg moduleNames: String): Array<String> {
val moduleNamesSet = moduleNames.toSet()
val list = mutableListOf<String>()
myProject.modules.forEach { module ->
if (module.name in moduleNamesSet) {
val outputDir = module.productionBuildTarget.outputDir!!
list.add(toSystemIndependentName(File("$outputDir/${module.name}.js").relativeTo(workDir).path))
list.add(toSystemIndependentName(File("$outputDir/${module.name}.meta.js").relativeTo(workDir).path))
val kjsmFiles = outputDir.walk().filter { it.isFile && it.extension.equals("kjsm", ignoreCase = true) }
list.addAll(kjsmFiles.map { toSystemIndependentName(it.relativeTo(workDir).path) })
}
}
return list.toTypedArray()
}
fun testKotlinJavaScriptProjectNewSourceRootTypes() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
checkOutputFilesList()
}
fun testKotlinJavaScriptProjectWithCustomOutputPaths() {
initProject(JS_STDLIB_WITHOUT_FACET)
buildAllModules().assertSuccessful()
checkOutputFilesList(File(workDir, "target"))
}
fun testKotlinJavaScriptProjectWithSourceMap() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
val sourceMapContent = File(getOutputDir(PROJECT_NAME), "$PROJECT_NAME.js.map").readText()
val expectedPath = "prefix-dir/src/pkg/test1.kt"
assertTrue("Source map file should contain relative path ($expectedPath)", sourceMapContent.contains("\"$expectedPath\""))
val librarySourceMapFile = File(getOutputDir(PROJECT_NAME), "lib/kotlin.js.map")
assertTrue("Source map for stdlib should be copied to $librarySourceMapFile", librarySourceMapFile.exists())
}
fun testKotlinJavaScriptProjectWithSourceMapRelativePaths() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
val sourceMapContent = File(getOutputDir(PROJECT_NAME), "$PROJECT_NAME.js.map").readText()
val expectedPath = "../../../src/pkg/test1.kt"
assertTrue("Source map file should contain relative path ($expectedPath)", sourceMapContent.contains("\"$expectedPath\""))
val librarySourceMapFile = File(getOutputDir(PROJECT_NAME), "lib/kotlin.js.map")
assertTrue("Source map for stdlib should be copied to $librarySourceMapFile", librarySourceMapFile.exists())
}
fun testKotlinJavaScriptProjectWithTwoModules() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
checkWhen(touch("module2/src/module2.kt"), null, k2jsOutput(ADDITIONAL_MODULE_NAME))
checkWhen(arrayOf(touch("src/test1.kt"), touch("module2/src/module2.kt")), null, k2jsOutput(PROJECT_NAME, ADDITIONAL_MODULE_NAME))
}
@WorkingDir("KotlinJavaScriptProjectWithTwoModules")
fun testKotlinJavaScriptProjectWithTwoModulesAndWithLibrary() {
initProject()
createKotlinJavaScriptLibraryArchive()
addDependency(KOTLIN_JS_LIBRARY, File(workDir, KOTLIN_JS_LIBRARY_JAR))
addKotlinJavaScriptStdlibDependency()
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithDirectoryAsStdlib() {
initProject()
setupKotlinJSFacet()
val jslibJar = PathUtil.kotlinPathsForDistDirectoryForTests.jsStdLibJarPath
val jslibDir = File(workDir, "KotlinJavaScript")
try {
Decompressor.Zip(jslibJar).extract(jslibDir)
}
catch (ex: IOException) {
throw IllegalStateException(ex.message)
}
addDependency("KotlinJavaScript", jslibDir)
buildAllModules().assertSuccessful()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
}
fun testKotlinJavaScriptProjectWithDirectoryAsLibrary() {
initProject(JS_STDLIB)
addDependency(KOTLIN_JS_LIBRARY, File(workDir, KOTLIN_JS_LIBRARY))
buildAllModules().assertSuccessful()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
}
fun testKotlinJavaScriptProjectWithLibrary() {
doTestWithKotlinJavaScriptLibrary()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
}
fun testKotlinJavaScriptProjectWithLibraryCustomOutputDir() {
doTestWithKotlinJavaScriptLibrary()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
}
fun testKotlinJavaScriptProjectWithLibraryNoCopy() {
doTestWithKotlinJavaScriptLibrary()
checkOutputFilesList()
checkWhen(touch("src/test1.kt"), null, k2jsOutput(PROJECT_NAME))
}
fun testKotlinJavaScriptProjectWithLibraryAndErrors() {
initProject(JS_STDLIB)
createKotlinJavaScriptLibraryArchive()
addDependency(KOTLIN_JS_LIBRARY, File(workDir, KOTLIN_JS_LIBRARY_JAR))
buildAllModules().assertFailed()
checkOutputFilesList()
}
fun testKotlinJavaScriptProjectWithEmptyDependencies() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptInternalFromSpecialRelatedModule() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithTests() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithTestsAndSeparateTestAndSrcModuleDependencies() {
initProject(JS_STDLIB)
buildAllModules().assertSuccessful()
}
fun testKotlinJavaScriptProjectWithTestsAndTestAndSrcModuleDependency() {
initProject(JS_STDLIB)
val buildResult = buildAllModules()
buildResult.assertSuccessful()
val warnings = buildResult.getMessages(BuildMessage.Kind.WARNING)
assertEquals("Warning about duplicate module definition: $warnings", 0, warnings.size)
}
fun testKotlinJavaScriptProjectWithTwoSrcModuleDependency() {
initProject(JS_STDLIB)
val buildResult = buildAllModules()
buildResult.assertSuccessful()
val warnings = buildResult.getMessages(BuildMessage.Kind.WARNING)
assertEquals("Warning about duplicate module definition: $warnings", 0, warnings.size)
}
fun testExcludeFolderInSourceRoot() {
doTest()
val module = myProject.modules[0]
assertFilesExistInOutput(module, "Foo.class")
assertFilesNotExistInOutput(module, *EXCLUDE_FILES)
checkWhen(
touch("src/foo.kt"), null,
arrayOf(klass("kotlinProject", "Foo"), module("kotlinProject"))
)
}
fun testExcludeModuleFolderInSourceRootOfAnotherModule() {
doTest()
for (module in myProject.modules) {
assertFilesExistInOutput(module, "Foo.class")
}
checkWhen(
touch("src/foo.kt"), null,
arrayOf(klass("kotlinProject", "Foo"), module("kotlinProject"))
)
checkWhen(
touch("src/module2/src/foo.kt"), null,
arrayOf(klass("module2", "Foo"), module("module2"))
)
}
fun testExcludeFileUsingCompilerSettings() {
doTest()
val module = myProject.modules[0]
assertFilesExistInOutput(module, "Foo.class", "Bar.class")
assertFilesNotExistInOutput(module, *EXCLUDE_FILES)
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/foo.kt"), null, arrayOf(module("kotlinProject"), klass("kotlinProject", "Foo")))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("src/foo.kt"), null, allClasses)
}
checkWhen(touch("src/Excluded.kt"), null, NOTHING)
checkWhen(touch("src/dir/YetAnotherExcluded.kt"), null, NOTHING)
}
fun testExcludeFolderNonRecursivelyUsingCompilerSettings() {
doTest()
val module = myProject.modules[0]
assertFilesExistInOutput(module, "Foo.class", "Bar.class")
assertFilesNotExistInOutput(module, *EXCLUDE_FILES)
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/foo.kt"), null, arrayOf(module("kotlinProject"), klass("kotlinProject", "Foo")))
checkWhen(touch("src/dir/subdir/bar.kt"), null, arrayOf(module("kotlinProject"), klass("kotlinProject", "Bar")))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("src/foo.kt"), null, allClasses)
checkWhen(touch("src/dir/subdir/bar.kt"), null, allClasses)
}
checkWhen(touch("src/dir/Excluded.kt"), null, NOTHING)
checkWhen(touch("src/dir/subdir/YetAnotherExcluded.kt"), null, NOTHING)
}
fun testExcludeFolderRecursivelyUsingCompilerSettings() {
doTest()
val module = myProject.modules[0]
assertFilesExistInOutput(module, "Foo.class", "Bar.class")
assertFilesNotExistInOutput(module, *EXCLUDE_FILES)
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/foo.kt"), null, arrayOf(module("kotlinProject"), klass("kotlinProject", "Foo")))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("src/foo.kt"), null, allClasses)
}
checkWhen(touch("src/exclude/Excluded.kt"), null, NOTHING)
checkWhen(touch("src/exclude/YetAnotherExcluded.kt"), null, NOTHING)
checkWhen(touch("src/exclude/subdir/Excluded.kt"), null, NOTHING)
checkWhen(touch("src/exclude/subdir/YetAnotherExcluded.kt"), null, NOTHING)
}
fun testKotlinProjectTwoFilesInOnePackage() {
doTest()
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/test1.kt"), null, packageClasses("kotlinProject", "src/test1.kt", "_DefaultPackage"))
checkWhen(touch("src/test2.kt"), null, packageClasses("kotlinProject", "src/test2.kt", "_DefaultPackage"))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("src/test1.kt"), null, allClasses)
checkWhen(touch("src/test2.kt"), null, allClasses)
}
checkWhen(arrayOf(del("src/test1.kt"), del("src/test2.kt")), NOTHING,
arrayOf(packagePartClass("kotlinProject", "src/test1.kt", "_DefaultPackage"),
packagePartClass("kotlinProject", "src/test2.kt", "_DefaultPackage"),
module("kotlinProject")))
assertFilesNotExistInOutput(myProject.modules[0], "_DefaultPackage.class")
}
fun testDefaultLanguageVersionCustomApiVersion() {
initProject(JVM_FULL_RUNTIME)
buildAllModules().assertFailed()
assertEquals(1, myProject.modules.size)
val module = myProject.modules.first()
val args = module.kotlinCompilerArguments
args.apiVersion = "1.4"
myProject.kotlinCommonCompilerArguments = args
buildAllModules().assertSuccessful()
}
fun testPureJavaProject() {
initProject(JVM_FULL_RUNTIME)
fun build() {
var someFilesCompiled = false
buildCustom(CanceledStatus.NULL, TestProjectBuilderLogger(), BuildResult()) {
project.setTestingContext(TestingContext(LookupTracker.DO_NOTHING, object : TestingBuildLogger {
override fun compilingFiles(files: Collection<File>, allRemovedFilesFiles: Collection<File>) {
someFilesCompiled = true
}
}))
}
assertFalse("Kotlin builder should return early if there are no Kotlin files", someFilesCompiled)
}
build()
rename("${workDir}/src/Test.java", "Test1.java")
build()
}
fun testKotlinJavaProject() {
doTestWithRuntime()
}
fun testJKJProject() {
doTestWithRuntime()
}
fun testKJKProject() {
doTestWithRuntime()
}
fun testKJCircularProject() {
doTestWithRuntime()
}
fun testJKJInheritanceProject() {
doTestWithRuntime()
}
fun testKJKInheritanceProject() {
doTestWithRuntime()
}
fun testCircularDependenciesNoKotlinFiles() {
doTest()
}
fun testCircularDependenciesDifferentPackages() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
// Check that outputs are located properly
assertFilesExistInOutput(findModule("module2"), "kt1/Kt1Kt.class")
assertFilesExistInOutput(findModule("kotlinProject"), "kt2/Kt2Kt.class")
result.assertSuccessful()
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/kt2.kt"), null, packageClasses("kotlinProject", "src/kt2.kt", "kt2.Kt2Kt"))
checkWhen(touch("module2/src/kt1.kt"), null, packageClasses("module2", "module2/src/kt1.kt", "kt1.Kt1Kt"))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("src/kt2.kt"), null, allClasses)
checkWhen(touch("module2/src/kt1.kt"), null, allClasses)
}
}
fun testCircularDependenciesSamePackage() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertSuccessful()
// Check that outputs are located properly
val facadeWithA = findFileInOutputDir(findModule("module1"), "test/AKt.class")
val facadeWithB = findFileInOutputDir(findModule("module2"), "test/BKt.class")
UsefulTestCase.assertSameElements(getMethodsOfClass(facadeWithA), "<clinit>", "a", "getA")
UsefulTestCase.assertSameElements(getMethodsOfClass(facadeWithB), "<clinit>", "b", "getB", "setB")
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("module1/src/a.kt"), null, packageClasses("module1", "module1/src/a.kt", "test.TestPackage"))
checkWhen(touch("module2/src/b.kt"), null, packageClasses("module2", "module2/src/b.kt", "test.TestPackage"))
}
else {
val allClasses = myProject.outputPaths()
checkWhen(touch("module1/src/a.kt"), null, allClasses)
checkWhen(touch("module2/src/b.kt"), null, allClasses)
}
}
fun testCircularDependenciesSamePackageWithTests() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertSuccessful()
// Check that outputs are located properly
val facadeWithA = findFileInOutputDir(findModule("module1"), "test/AKt.class")
val facadeWithB = findFileInOutputDir(findModule("module2"), "test/BKt.class")
UsefulTestCase.assertSameElements(getMethodsOfClass(facadeWithA), "<clinit>", "a", "funA", "getA")
UsefulTestCase.assertSameElements(getMethodsOfClass(facadeWithB), "<clinit>", "b", "funB", "getB", "setB")
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("module1/src/a.kt"), null, packageClasses("module1", "module1/src/a.kt", "test.TestPackage"))
checkWhen(touch("module2/src/b.kt"), null, packageClasses("module2", "module2/src/b.kt", "test.TestPackage"))
}
else {
val allProductionClasses = myProject.outputPaths(tests = false)
checkWhen(touch("module1/src/a.kt"), null, allProductionClasses)
checkWhen(touch("module2/src/b.kt"), null, allProductionClasses)
}
}
fun testInternalFromAnotherModule() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertFailed()
result.checkErrors()
}
fun testInternalFromSpecialRelatedModule() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
val classpath = listOf("out/production/module1", "out/test/module2").map { File(workDir, it).toURI().toURL() }.toTypedArray()
val clazz = URLClassLoader(classpath).loadClass("test2.BarKt")
clazz.getMethod("box").invoke(null)
}
fun testCircularDependenciesInternalFromAnotherModule() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertFailed()
result.checkErrors()
}
fun testCircularDependenciesWrongInternalFromTests() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertFailed()
result.checkErrors()
}
fun testCircularDependencyWithReferenceToOldVersionLib() {
initProject(JVM_MOCK_RUNTIME)
val libraryJar = MockLibraryUtilExt.compileJvmLibraryToJar(workDir.absolutePath + File.separator + "oldModuleLib/src", "module-lib")
AbstractKotlinJpsBuildTestCase.addDependency(JpsJavaDependencyScope.COMPILE, listOf(findModule("module1"), findModule("module2")), false, "module-lib", libraryJar)
val result = buildAllModules()
result.assertSuccessful()
}
fun testDependencyToOldKotlinLib() {
initProject()
val libraryJar = MockLibraryUtilExt.compileJvmLibraryToJar(workDir.absolutePath + File.separator + "oldModuleLib/src", "module-lib")
AbstractKotlinJpsBuildTestCase.addDependency(JpsJavaDependencyScope.COMPILE, listOf(findModule("module")), false, "module-lib", libraryJar)
addKotlinStdlibDependency()
val result = buildAllModules()
result.assertSuccessful()
}
fun testDevKitProject() {
initProject(JVM_MOCK_RUNTIME)
val module = myProject.modules.single()
assertEquals(module.moduleType, JpsPluginModuleType.INSTANCE)
buildAllModules().assertSuccessful()
assertFilesExistInOutput(module, "TestKt.class")
}
fun testAccessToInternalInProductionFromTests() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertSuccessful()
}
private fun createKotlinJavaScriptLibraryArchive() {
val jarFile = File(workDir, KOTLIN_JS_LIBRARY_JAR)
try {
val zip = ZipOutputStream(FileOutputStream(jarFile))
ZipUtil.addDirToZipRecursively(zip, jarFile, File(PATH_TO_KOTLIN_JS_LIBRARY), "", null, null)
zip.close()
}
catch (ex: FileNotFoundException) {
throw IllegalStateException(ex.message)
}
catch (ex: IOException) {
throw IllegalStateException(ex.message)
}
}
protected fun checkOutputFilesList(outputDir: File = productionOutputDir) {
if (!expectedOutputFile.exists()) {
expectedOutputFile.writeText("")
throw IllegalStateException("$expectedOutputFile did not exist. Created empty file.")
}
val sb = StringBuilder()
val p = Printer(sb, " ")
outputDir.printFilesRecursively(p)
UsefulTestCase.assertSameLinesWithFile(expectedOutputFile.canonicalPath, sb.toString(), true)
}
private fun File.printFilesRecursively(p: Printer) {
val files = listFiles() ?: return
for (file in files.sortedBy { it.name }) {
when {
file.isFile -> {
p.println(file.name)
}
file.isDirectory -> {
p.println(file.name + "/")
p.pushIndent()
file.printFilesRecursively(p)
p.popIndent()
}
}
}
}
private val productionOutputDir
get() = File(workDir, "out/production")
private fun getOutputDir(moduleName: String): File = File(productionOutputDir, moduleName)
fun testReexportedDependency() {
initProject()
addKotlinStdlibDependency(myProject.modules.filter { module -> module.name == "module2" }, true)
buildAllModules().assertSuccessful()
}
fun testCheckIsCancelledIsCalledOftenEnough() {
val classCount = 30
val methodCount = 30
fun generateFiles() {
val srcDir = File(workDir, "src")
srcDir.mkdirs()
for (i in 0..classCount) {
val code = buildString {
appendLine("package foo")
appendLine("class Foo$i {")
for (j in 0..methodCount) {
appendLine(" fun get${j*j}(): Int = square($j)")
}
appendLine("}")
}
File(srcDir, "Foo$i.kt").writeText(code)
}
}
generateFiles()
initProject(JVM_MOCK_RUNTIME)
var checkCancelledCalledCount = 0
val countingCancelledStatus = CanceledStatus {
checkCancelledCalledCount++
false
}
val logger = TestProjectBuilderLogger()
val buildResult = BuildResult()
buildCustom(countingCancelledStatus, logger, buildResult)
buildResult.assertSuccessful()
assert(checkCancelledCalledCount > classCount) {
"isCancelled should be called at least once per class. Expected $classCount, but got $checkCancelledCalledCount"
}
}
fun testCancelKotlinCompilation() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
val module = myProject.modules[0]
assertFilesExistInOutput(module, "foo/Bar.class")
val buildResult = BuildResult()
val canceledStatus = object : CanceledStatus {
var checkFromIndex = 0
override fun isCanceled(): Boolean {
val messages = buildResult.getMessages(BuildMessage.Kind.INFO)
for (i in checkFromIndex until messages.size) {
if (messages[i].messageText.matches("kotlinc-jvm .+ \\(JRE .+\\)".toRegex())) {
return true
}
}
checkFromIndex = messages.size
return false
}
}
touch("src/Bar.kt").apply()
buildCustom(canceledStatus, TestProjectBuilderLogger(), buildResult)
assertCanceled(buildResult)
}
fun testFileDoesNotExistWarning() {
fun absoluteFiles(vararg paths: String): Array<File> =
paths.map { File(it).absoluteFile }.toTypedArray()
initProject(JVM_MOCK_RUNTIME)
val filesToBeReported = absoluteFiles("badroot.jar", "some/test.class")
val otherFiles = absoluteFiles("test/other/file.xml", "some/other/baddir")
addDependency(
JpsJavaDependencyScope.COMPILE,
listOf(findModule("module")),
false,
"LibraryWithBadRoots",
*(filesToBeReported + otherFiles),
)
val result = buildAllModules()
result.assertSuccessful()
val actualWarnings = result.getMessages(BuildMessage.Kind.WARNING).map { it.messageText }
val expectedWarnings = filesToBeReported.map { "Classpath entry points to a non-existent location: $it" }
val expectedText = expectedWarnings.sorted().joinToString("\n")
val actualText = actualWarnings.sorted().joinToString("\n")
Assert.assertEquals(expectedText, actualText)
}
fun testHelp() {
initProject()
val result = buildAllModules()
result.assertSuccessful()
val warning = result.getMessages(BuildMessage.Kind.WARNING).single()
val expectedText = StringUtil.convertLineSeparators(Usage.render(K2JVMCompiler(), K2JVMCompilerArguments()))
Assert.assertEquals(expectedText, warning.messageText)
}
fun testWrongArgument() {
initProject()
val result = buildAllModules()
result.assertFailed()
val errors = result.getMessages(BuildMessage.Kind.ERROR).joinToString("\n\n") { it.messageText }
Assert.assertEquals("Invalid argument: -abcdefghij-invalid-argument", errors)
}
fun testCodeInKotlinPackage() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertFailed()
val errors = result.getMessages(BuildMessage.Kind.ERROR)
Assert.assertEquals("Only the Kotlin standard library is allowed to use the 'kotlin' package", errors.single().messageText)
}
fun testDoNotCreateUselessKotlinIncrementalCaches() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
val storageRoot = BuildDataPathsImpl(myDataStorageRoot).dataStorageRoot
assertFalse(File(storageRoot, "targets/java-test/kotlinProject/kotlin").exists())
assertFalse(File(storageRoot, "targets/java-production/kotlinProject/kotlin").exists())
}
fun testDoNotCreateUselessKotlinIncrementalCachesForDependentTargets() {
initProject(JVM_MOCK_RUNTIME)
buildAllModules().assertSuccessful()
if (IncrementalCompilation.isEnabledForJvm()) {
checkWhen(touch("src/utils.kt"), null, packageClasses("kotlinProject", "src/utils.kt", "_DefaultPackage"))
}
else {
val allClasses = findModule("kotlinProject").outputFilesPaths()
checkWhen(touch("src/utils.kt"), null, allClasses.toTypedArray())
}
val storageRoot = BuildDataPathsImpl(myDataStorageRoot).dataStorageRoot
assertFalse(File(storageRoot, "targets/java-production/kotlinProject/kotlin").exists())
assertFalse(File(storageRoot, "targets/java-production/module2/kotlin").exists())
}
fun testKotlinProjectWithEmptyProductionOutputDir() {
initProject(JVM_MOCK_RUNTIME)
val result = buildAllModules()
result.assertFailed()
result.checkErrors()
}
fun testKotlinProjectWithEmptyTestOutputDir() {
doTest()
}
fun testKotlinProjectWithEmptyProductionOutputDirWithoutSrcDir() {
doTest()
}
fun testKotlinProjectWithEmptyOutputDirInSomeModules() {
doTest()
}
fun testGetDependentTargets() {
fun addModuleWithSourceAndTestRoot(name: String): JpsModule {
return addModule(name, "src/").apply {
contentRootsList.addUrl(JpsPathUtil.pathToUrl("test/"))
addSourceRoot(JpsPathUtil.pathToUrl("test/"), JavaSourceRootType.TEST_SOURCE)
}
}
// c -> b -exported-> a
// c2 -> b2 ------------^
val a = addModuleWithSourceAndTestRoot("a")
val b = addModuleWithSourceAndTestRoot("b")
val c = addModuleWithSourceAndTestRoot("c")
val b2 = addModuleWithSourceAndTestRoot("b2")
val c2 = addModuleWithSourceAndTestRoot("c2")
JpsModuleRootModificationUtil.addDependency(b, a, JpsJavaDependencyScope.COMPILE, /*exported =*/ true)
JpsModuleRootModificationUtil.addDependency(c, b, JpsJavaDependencyScope.COMPILE, /*exported =*/ false)
JpsModuleRootModificationUtil.addDependency(b2, a, JpsJavaDependencyScope.COMPILE, /*exported =*/ false)
JpsModuleRootModificationUtil.addDependency(c2, b2, JpsJavaDependencyScope.COMPILE, /*exported =*/ false)
val actual = StringBuilder()
buildCustom(CanceledStatus.NULL, TestProjectBuilderLogger(), BuildResult()) {
project.setTestingContext(TestingContext(LookupTracker.DO_NOTHING, object : TestingBuildLogger {
override fun chunkBuildStarted(context: CompileContext, chunk: ModuleChunk) {
actual.append("Targets dependent on ${chunk.targets.joinToString()}:\n")
val dependentRecursively = mutableSetOf<KotlinChunk>()
context.kotlin.getChunk(chunk)!!.collectDependentChunksRecursivelyExportedOnly(dependentRecursively)
dependentRecursively.asSequence().map { it.targets.joinToString() }.sorted().joinTo(actual, "\n")
actual.append("\n---------\n")
}
override fun afterChunkBuildStarted(context: CompileContext, chunk: ModuleChunk) {}
override fun markedAsComplementaryFiles(files: Collection<File>) {}
override fun invalidOrUnusedCache(
chunk: KotlinChunk?,
target: KotlinModuleBuildTarget<*>?,
attributesDiff: CacheAttributesDiff<*>
) {}
override fun addCustomMessage(message: String) {}
override fun buildFinished(exitCode: ModuleLevelBuilder.ExitCode) {}
override fun markedAsDirtyBeforeRound(files: Iterable<File>) {}
override fun markedAsDirtyAfterRound(files: Iterable<File>) {}
}))
}
val expectedFile = File(getCurrentTestDataRoot(), "expected.txt")
KotlinTestUtils.assertEqualsToFile(expectedFile, actual.toString())
}
fun testJre11() {
val jdk11Path = KtTestUtil.getJdk11Home().absolutePath
val jdk = myModel.global.addSdk(JDK_NAME, jdk11Path, "11", JpsJavaSdkType.INSTANCE)
jdk.addRoot(StandardFileSystems.JRT_PROTOCOL_PREFIX + jdk11Path + URLUtil.JAR_SEPARATOR + "java.base", JpsOrderRootType.COMPILED)
loadProject(workDir.absolutePath + File.separator + PROJECT_NAME + ".ipr")
addKotlinStdlibDependency()
buildAllModules().assertSuccessful()
}
fun testCustomDestination() {
loadProject(workDir.absolutePath + File.separator + PROJECT_NAME + ".ipr")
addKotlinStdlibDependency()
buildAllModules().apply {
assertSuccessful()
val aClass = File(workDir, "customOut/A.class")
assert(aClass.exists()) { "$aClass does not exist!" }
val warnings = getMessages(BuildMessage.Kind.WARNING)
assert(warnings.isEmpty()) { "Unexpected warnings: \n${warnings.joinToString("\n")}" }
}
}
fun testKotlinLombokProjectBuild() {
initProject(LOMBOK)
buildAllModules().assertSuccessful()
}
@WorkingDir("KotlinProject")
fun testModuleRebuildOnPluginClasspathsChange() {
initProject(JVM_MOCK_RUNTIME)
myProject.modules.forEach {
val facet = KotlinFacetSettings()
facet.useProjectSettings = false
facet.compilerArguments = K2JVMCompilerArguments()
facet.compilerArguments?.pluginClasspaths = arrayOf(PathUtil.kotlinPathsForDistDirectoryForTests.lombokPluginJarPath.path)
it.container.setChild(
JpsKotlinFacetModuleExtension.KIND,
JpsKotlinFacetModuleExtension(facet)
)
}
buildAllModules().assertSuccessful()
myProject.modules.forEach {
val facet = KotlinFacetSettings()
facet.useProjectSettings = false
facet.compilerArguments = K2JVMCompilerArguments()
facet.compilerArguments?.pluginClasspaths = arrayOf(
PathUtil.kotlinPathsForDistDirectoryForTests.lombokPluginJarPath.path,
PathUtil.kotlinPathsForDistDirectoryForTests.allOpenPluginJarPath.path
)
it.container.setChild(
JpsKotlinFacetModuleExtension.KIND,
JpsKotlinFacetModuleExtension(facet)
)
}
checkWhen(emptyArray(), null, packageClasses("kotlinProject", "src/test1.kt", "Test1Kt"))
}
fun testBuildAfterGdwBuild() {
initProject(JVM_FULL_RUNTIME)
findModule("module2").let {
val facet = KotlinFacetSettings()
facet.useProjectSettings = false
facet.compilerArguments = K2JVMCompilerArguments()
val libraryName = "module1-1.0-SNAPSHOT"
val libraryJar = MockLibraryUtilExt.compileJvmLibraryToJar(workDir.resolve("module1AsLib").absolutePath, libraryName)
val module1Lib = this.workDir.resolve("module1").resolve("build").resolve("libs").resolve("$libraryName.jar")
Files.createDirectories(module1Lib.parentFile.toPath())
Files.copy(libraryJar.toPath(), module1Lib.toPath(), StandardCopyOption.REPLACE_EXISTING)
assert(module1Lib.exists())
(facet.compilerArguments as K2JVMCompilerArguments).classpath = module1Lib.path
it.container.setChild(
JpsKotlinFacetModuleExtension.KIND,
JpsKotlinFacetModuleExtension(facet)
)
}
buildAllModules().assertSuccessful()
}
private fun BuildResult.checkErrors() {
val actualErrors = getMessages(BuildMessage.Kind.ERROR)
.map { it as CompilerMessage }
.map { "${it.messageText} at line ${it.line}, column ${it.column}" }.sorted().joinToString("\n")
val expectedFile = File(getCurrentTestDataRoot(), "errors.txt")
KotlinTestUtils.assertEqualsToFile(expectedFile, actualErrors)
}
private fun getCurrentTestDataRoot() = File(AbstractKotlinJpsBuildTestCase.TEST_DATA_PATH + "general/" + getTestName(false))
private fun buildCustom(
canceledStatus: CanceledStatus,
logger: TestProjectBuilderLogger,
buildResult: BuildResult,
setupProject: ProjectDescriptor.() -> Unit = {}
) {
val scopeBuilder = CompileScopeTestBuilder.make().allModules()
val descriptor = this.createProjectDescriptor(BuildLoggingManager(logger))
descriptor.setupProject()
try {
val builder = IncProjectBuilder(descriptor, BuilderRegistry.getInstance(), this.myBuildParams, canceledStatus, true)
builder.addMessageHandler(buildResult)
builder.build(scopeBuilder.build(), false)
}
finally {
descriptor.dataManager.flush(false)
descriptor.release()
}
}
private fun assertCanceled(buildResult: BuildResult) {
val list = buildResult.getMessages(BuildMessage.Kind.INFO)
assertTrue("The build has been canceled" == list.last().messageText)
}
private fun findModule(name: String): JpsModule {
for (module in myProject.modules) {
if (module.name == name) {
return module
}
}
throw IllegalStateException("Couldn't find module $name")
}
protected fun checkWhen(action: Action, pathsToCompile: Array<String>?, pathsToDelete: Array<String>?) {
checkWhen(arrayOf(action), pathsToCompile, pathsToDelete)
}
protected fun checkWhen(actions: Array<Action>, pathsToCompile: Array<String>?, pathsToDelete: Array<String>?) {
for (action in actions) {
action.apply()
}
buildAllModules().assertSuccessful()
if (pathsToCompile != null) {
assertCompiled(KotlinBuilder.KOTLIN_BUILDER_NAME, *pathsToCompile)
}
if (pathsToDelete != null) {
assertDeleted(*pathsToDelete)
}
}
protected fun packageClasses(moduleName: String, fileName: String, packageClassFqName: String): Array<String> {
return arrayOf(module(moduleName), packagePartClass(moduleName, fileName, packageClassFqName))
}
protected fun packagePartClass(moduleName: String, fileName: String, packageClassFqName: String): String {
val path = FileUtilRt.toSystemIndependentName(File(workDir, fileName).absolutePath)
val fakeVirtualFile = object : LightVirtualFile(path.substringAfterLast('/')) {
override fun getPath(): String {
// strip extra "/" from the beginning
return path.substring(1)
}
}
val packagePartFqName = PackagePartClassUtils.getDefaultPartFqName(FqName(packageClassFqName), fakeVirtualFile)
return klass(moduleName, AsmUtil.internalNameByFqNameWithoutInnerClasses(packagePartFqName))
}
private fun JpsProject.outputPaths(production: Boolean = true, tests: Boolean = true) =
modules.flatMap { it.outputFilesPaths(production = production, tests = tests) }.toTypedArray()
private fun JpsModule.outputFilesPaths(production: Boolean = true, tests: Boolean = true): List<String> {
val outputFiles = arrayListOf<File>()
if (production) {
prodOut.walk().filterTo(outputFiles) { it.isFile }
}
if (tests) {
testsOut.walk().filterTo(outputFiles) { it.isFile }
}
return outputFiles.map { FileUtilRt.toSystemIndependentName(it.relativeTo(workDir).path) }
}
private val JpsModule.prodOut: File
get() = outDir(forTests = false)
private val JpsModule.testsOut: File
get() = outDir(forTests = true)
private fun JpsModule.outDir(forTests: Boolean) =
JpsJavaExtensionService.getInstance().getOutputDirectory(this, forTests)!!
protected enum class Operation {
CHANGE,
DELETE
}
protected fun touch(path: String): Action = Action(Operation.CHANGE, path)
protected fun del(path: String): Action = Action(Operation.DELETE, path)
// TODO inline after KT-3974 will be fixed
protected fun touch(file: File): Unit = change(file.absolutePath)
protected inner class Action constructor(private val operation: Operation, private val path: String) {
fun apply() {
val file = File(workDir, path)
when (operation) {
Operation.CHANGE ->
touch(file)
Operation.DELETE ->
assertTrue("Can not delete file \"" + file.absolutePath + "\"", file.delete())
}
}
}
}