fixed detection of source folder creation

This commit is contained in:
Sergei Vorobyov
2018-12-07 14:45:44 +03:00
committed by Sergei Vorobyov
parent 48d798af87
commit 421dc9ace2
7 changed files with 488 additions and 29 deletions
@@ -3,11 +3,14 @@ package com.intellij.openapi.externalSystem.service.project.manage
import com.intellij.ide.projectView.actions.MarkRootActionBase
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.TransactionGuard
import com.intellij.openapi.externalSystem.util.DisposeAwareProjectChange
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.PathPrefixTreeMapImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.Computable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.*
@@ -20,13 +23,12 @@ class SourceFolderManagerImpl(private val project: Project) : SourceFolderManage
private var isDisposed = false
private val mutex = Any()
private val postponedSourceFolderCreator = PostponedSourceFolderCreator()
private val sourceFolders = THashMap<String, SourceFolderModel>(FileUtil.PATH_HASHING_STRATEGY)
private val sourceFolders = PathPrefixTreeMapImpl<SourceFolderModel>()
private val sourceFoldersByModule = THashMap<String, ModuleModel>()
override fun addSourceFolder(module: Module, url: String, type: JpsModuleSourceRootType<*>) {
val sourceFolder = SourceFolderModel(module, type, "")
synchronized(mutex) {
sourceFolders[url] = sourceFolder
sourceFolders[url] = SourceFolderModel(module, url, type, "")
val moduleModel = sourceFoldersByModule.getOrPut(module.name) {
ModuleModel(module).also {
Disposer.register(module, it)
@@ -34,6 +36,10 @@ class SourceFolderManagerImpl(private val project: Project) : SourceFolderManage
}
moduleModel.sourceFolders.add(url)
}
TransactionGuard.getInstance().submitTransactionLater(this, Runnable {
val virtualFileManager = VirtualFileManager.getInstance()
virtualFileManager.refreshAndFindFileByUrl(url)
})
}
override fun setSourceFolderPackagePrefix(url: String, packagePrefix: String) {
@@ -57,42 +63,60 @@ class SourceFolderManagerImpl(private val project: Project) : SourceFolderManage
virtualFileManager.removeVirtualFileListener(postponedSourceFolderCreator)
}
private fun remove(url: String): SourceFolderModel? {
return synchronized(mutex) {
sourceFolders.remove(url)
}
}
fun isDisposed() = isDisposed
fun getSourceFolders(moduleName: String) = synchronized(mutex) {
sourceFoldersByModule[moduleName]?.sourceFolders
}
private fun unsafeRemoveSourceFolder(url: String) {
val sourceFolder = sourceFolders.remove(url) ?: return
val module = sourceFolder.module
val moduleModel = sourceFoldersByModule[module.name] ?: return
val sourceFolders = moduleModel.sourceFolders
sourceFolders.remove(url)
if (sourceFolders.isEmpty()) {
sourceFoldersByModule.remove(module.name)
}
}
private inner class PostponedSourceFolderCreator : VirtualFileListener {
override fun fileCreated(event: VirtualFileEvent) {
val sourceFoldersToChange = ArrayList<SourceFolderModel>()
val virtualFileManager = VirtualFileManager.getInstance()
synchronized(mutex) {
for (sourceFolder in sourceFolders.getAllDescendants(event.file.url)) {
val sourceFolderFile = ExternalSystemApiUtil.doWriteAction(Computable<VirtualFile> {
virtualFileManager.refreshAndFindFileByUrl(sourceFolder.url)
})
if (sourceFolderFile != null && sourceFolderFile.isValid) {
sourceFoldersToChange.add(sourceFolder)
unsafeRemoveSourceFolder(sourceFolder.url)
}
}
}
ExternalSystemApiUtil.executeProjectChangeAction(false, object : DisposeAwareProjectChange(project) {
override fun execute() {
val sourceFolderUrl = event.file.url
val (module, type, packagePrefix) = remove(sourceFolderUrl) ?: return
val moduleManager = ModuleRootManager.getInstance(module)
val modifiableModuleModel = moduleManager.modifiableModel
try {
val contentEntry = MarkRootActionBase.findContentEntry(modifiableModuleModel, event.file)
if (contentEntry != null) {
val sourceFolder = contentEntry.addSourceFolder(sourceFolderUrl, type)
sourceFolder.packagePrefix = packagePrefix
for ((module, url, type, packagePrefix) in sourceFoldersToChange) {
val moduleManager = ModuleRootManager.getInstance(module)
val modifiableModuleModel = moduleManager.modifiableModel
try {
val contentEntry = MarkRootActionBase.findContentEntry(modifiableModuleModel, event.file)
if (contentEntry != null) {
val sourceFolder = contentEntry.addSourceFolder(url, type)
sourceFolder.packagePrefix = packagePrefix
}
}
finally {
modifiableModuleModel.commit()
}
}
finally {
modifiableModuleModel.commit()
}
}
})
}
}
private data class SourceFolderModel(val module: Module, val type: JpsModuleSourceRootType<*>, var packagePrefix: String)
private data class SourceFolderModel(val module: Module, val url: String, val type: JpsModuleSourceRootType<*>, var packagePrefix: String)
private inner class ModuleModel(val module: Module, val sourceFolders: MutableSet<String>) : Disposable {
constructor(module: Module) : this(module, THashSet(FileUtil.PATH_HASHING_STRATEGY))
@@ -0,0 +1,34 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.util
/**
* [PrefixTreeMap] uses keys, that is specified by the file system paths.
* @see PrefixTreeMap
*/
interface PathPrefixTreeMap<V> : PrefixTreeMap<String, V> {
/**
* @see PrefixTreeMap.get
*/
operator fun get(path: String): V?
/**
* @see PrefixTreeMap.set
*/
operator fun set(path: String, value: V): V?
/**
* @see PrefixTreeMap.remove
*/
fun remove(path: String): V?
/**
* @see PrefixTreeMap.contains
*/
fun contains(path: String): Boolean
/**
* @see PrefixTreeMap.getAllDescendants
*/
fun getAllDescendants(path: String): List<V>
}
@@ -0,0 +1,25 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.util
import com.intellij.openapi.util.io.FileUtil
class PathPrefixTreeMapImpl<V>(
private val pathSeparator: String = "/",
private val removeLastSlash: Boolean = true
) : PathPrefixTreeMap<V>, PrefixTreeMapImpl<String, V>(FileUtil.PATH_HASHING_STRATEGY) {
override operator fun get(path: String): V? = get(path.toPrefixList())
override operator fun set(path: String, value: V): V? = set(path.toPrefixList(), value)
override fun remove(path: String): V? = remove(path.toPrefixList())
override fun contains(path: String): Boolean = contains(path.toPrefixList())
override fun getAllDescendants(path: String): List<V> = getAllDescendants(path.toPrefixList())
private fun String.toPrefixList(): List<String> {
val path = if (removeLastSlash) removeSuffix(pathSeparator) else this
return path.split(pathSeparator)
}
}
@@ -0,0 +1,49 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.util
/**
* Map for fast finding values by the prefix of them keys
*/
interface PrefixTreeMap<K, V> {
/**
* Gets value from the storage by the specified [path]
*
* @param path is key identificator
* @return stored value with the specified [path], null if [path] not found
*/
operator fun get(path: List<K>): V?
/**
* Sets value in the storage by the specified [path]
*
* @param path is key identificator
* @param value is value to store
* @return previous stored value with the specified [path], null if previous [path] not found
*/
operator fun set(path: List<K>, value: V): V?
/**
* Removes value from the storage by the specified [path]
*
* @param path is key identificator
* @return stored value with the specified [path], null if stored [path] not found
*/
fun remove(path: List<K>): V?
/**
* Checks existence of [path] in the storage
*
* @param path is key identificator
* @return result of existence checking
*/
fun contains(path: List<K>): Boolean
/**
* Gets all values with a key, for which [path] is an ancestor
*
* @param path is key identificator
* @return list of stored values, [emptyList] if [path] not found
*/
fun getAllDescendants(path: List<K>): List<V>
}
@@ -0,0 +1,93 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.util
import com.intellij.util.containers.FList
import gnu.trove.THashMap
import gnu.trove.TObjectHashingStrategy
open class PrefixTreeMapImpl<K, V>(private val strategy: TObjectHashingStrategy<K>? = null) : PrefixTreeMap<K, V> {
private val root = Node()
override operator fun get(path: List<K>) = root.get(path.toFList())?.getValue()
override operator fun set(path: List<K>, value: V) = root.put(path.toFList(), value)
override fun remove(path: List<K>) = root.remove(path.toFList())
override fun contains(path: List<K>) = root.contains(path.toFList())
override fun getAllDescendants(path: List<K>) = root.get(path.toFList())?.getAllDescendants() ?: emptyList()
private inner class Node {
private val children = strategy?.let { THashMap<K, Node>(it) } ?: THashMap()
private val isLeaf get() = children.isEmpty
private val isInvalid get() = isLeaf && !isPresent
private var isPresent: Boolean = false
private var value: V? = null
fun getValue() = value
fun put(path: FList<K>, value: V): V? {
val (head, tail) = path
val child = children.getOrPut(head) { Node() }
if (tail.isEmpty()) {
val previousValue = child.value
child.value = value
child.isPresent = true
return previousValue
}
else return child.put(tail, value)
}
fun remove(path: FList<K>): V? {
val (head, tail) = path
val child = children[head] ?: return null
if (tail.isEmpty()) {
val value = child.value
child.value = null
child.isPresent = false
return value
}
else {
val result = child.remove(tail)
if (child.isInvalid) {
children.remove(head)
}
return result
}
}
fun contains(path: FList<K>): Boolean {
val (head, tail) = path
val child = children[head] ?: return false
return when {
tail.isEmpty() -> child.isPresent
else -> child.contains(tail)
}
}
fun get(path: FList<K>): Node? {
val (head, tail) = path
val child = children[head] ?: return null
return when {
tail.isEmpty() -> child
else -> child.get(tail)
}
}
fun getAllDescendants(): List<V> {
val result = children.flatMap { it.value.getAllDescendants() }.toMutableList()
value?.let { result.add(it) }
return result
}
}
companion object {
private operator fun <E> FList<E>.component1() = head
private operator fun <E> FList<E>.component2() = tail
private fun <E> List<E>.toFList() = FList.createFromReversed(asReversed())
}
}
@@ -0,0 +1,241 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.service.project.manage
import com.intellij.openapi.externalSystem.util.PathPrefixTreeMapImpl
import com.intellij.testFramework.UsefulTestCase
import org.junit.Test
class PathPrefixTreeMapImplTest : UsefulTestCase() {
@Test
fun `test map filling`() {
val map = PathPrefixTreeMapImpl<Int>()
map["C://path/to/my/dir3"] = 30
map["C://path/to/my/dir4"] = 10
map["C://path/to/dir1"] = 11
map["C://path/to/dir2"] = 21
map["C://path/to/my"] = 43
map["C://path"] = 13
assertEquals(map["C://path/to/my/dir1"], null)
assertEquals(map["C://path/to/my/dir2"], null)
assertEquals(map["C://path/to/my/dir3"], 30)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1"], 11)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3"], null)
assertEquals(map["C://path/to/dir4"], null)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertFalse(map.contains("C://path/to/my/dir1"))
assertFalse(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertFalse(map.contains("C://path/to/dir3"))
assertFalse(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
map["C://path/to/my/dir1"] = 10
map["C://path/to/my/dir2"] = 20
map["C://path/to/dir3"] = 30
map["C://path/to/dir4"] = 11
assertEquals(map["C://path/to/my/dir1"], 10)
assertEquals(map["C://path/to/my/dir2"], 20)
assertEquals(map["C://path/to/my/dir3"], 30)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1"], 11)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3"], 30)
assertEquals(map["C://path/to/dir4"], 11)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertTrue(map.contains("C://path/to/my/dir1"))
assertTrue(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertTrue(map.contains("C://path/to/dir3"))
assertTrue(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
}
@Test
fun `test map removes`() {
val map = PathPrefixTreeMapImpl<Int>()
map["C://path/to/my/dir1"] = 10
map["C://path/to/my/dir2"] = 20
map["C://path/to/my/dir3"] = 30
map["C://path/to/my/dir4"] = 10
map["C://path/to/dir1"] = 11
map["C://path/to/dir2"] = 21
map["C://path/to/dir3"] = 30
map["C://path/to/dir4/"] = 11
map["C://path/to/my"] = 43
map["C://path"] = 13
assertEquals(map["C://path/to/my/dir1"], 10)
assertEquals(map["C://path/to/my/dir2"], 20)
assertEquals(map["C://path/to/my/dir3"], 30)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1"], 11)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3"], 30)
assertEquals(map["C://path/to/dir4"], 11)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertTrue(map.contains("C://path/to/my/dir1"))
assertTrue(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertTrue(map.contains("C://path/to/dir3"))
assertTrue(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
map.remove("C://path/to/my/dir1/")
map.remove("C://path/to/my/dir2")
map.remove("C://path/to/dir3")
map.remove("C://path/to/dir4")
assertEquals(map["C://path/to/my/dir1"], null)
assertEquals(map["C://path/to/my/dir2"], null)
assertEquals(map["C://path/to/my/dir3"], 30)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1"], 11)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3/"], null)
assertEquals(map["C://path/to/dir4"], null)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertFalse(map.contains("C://path/to/my/dir1"))
assertFalse(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertFalse(map.contains("C://path/to/dir3"))
assertFalse(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
}
@Test
fun `test containing nullable values`() {
val map = PathPrefixTreeMapImpl<Int?>()
map["C://path/to/my/dir1"] = null
map["C://path/to/my/dir2"] = 20
map["C://path/to/my/dir3/"] = null
map["C://path/to/my/dir4/"] = 10
map["C://path/to/dir1"] = null
map["C://path/to/dir2"] = 21
map["C://path/to/dir3"] = null
map["C://path/to/dir4"] = 11
map["C://path/to/my"] = 43
map["C://path"] = 13
assertEquals(map["C://path/to/my/dir1"], null)
assertEquals(map["C://path/to/my/dir2"], 20)
assertEquals(map["C://path/to/my/dir3"], null)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1"], null)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3"], null)
assertEquals(map["C://path/to/dir4/"], 11)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertTrue(map.contains("C://path/to/my/dir1"))
assertTrue(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4/"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertTrue(map.contains("C://path/to/dir3"))
assertTrue(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
map.remove("C://path/to/my/dir1")
map.remove("C://path/to/my/dir2")
map.remove("C://path/to/dir3")
map.remove("C://path/to/dir4")
assertEquals(map["C://path/to/my/dir1"], null)
assertEquals(map["C://path/to/my/dir2"], null)
assertEquals(map["C://path/to/my/dir3"], null)
assertEquals(map["C://path/to/my/dir4"], 10)
assertEquals(map["C://path/to/dir1/"], null)
assertEquals(map["C://path/to/dir2"], 21)
assertEquals(map["C://path/to/dir3"], null)
assertEquals(map["C://path/to/dir4"], null)
assertEquals(map["C://path/to/my"], 43)
assertEquals(map["C://path"], 13)
assertFalse(map.contains("C://path/to/my/dir1/"))
assertFalse(map.contains("C://path/to/my/dir2"))
assertTrue(map.contains("C://path/to/my/dir3"))
assertTrue(map.contains("C://path/to/my/dir4"))
assertTrue(map.contains("C://path/to/dir1"))
assertTrue(map.contains("C://path/to/dir2"))
assertFalse(map.contains("C://path/to/dir3"))
assertFalse(map.contains("C://path/to/dir4"))
assertTrue(map.contains("C://path/to/my"))
assertTrue(map.contains("C://path"))
}
@Test
fun `test get all elements under dir`() {
val map = PathPrefixTreeMapImpl<Int?>()
map["C://path/to/my/dir1/"] = 10
map["C://path/to/my/dir2"] = 20
map["C://path/to/my/dir3/"] = 30
map["C://path/to/my/dir4"] = 10
map["C://path/to/dir1"] = 11
map["C://path/to/dir2/"] = 21
map["C://path/to/dir3"] = 30
map["C://path/to/dir4/"] = 11
map["C://path/to/my"] = 43
map["C://path"] = 13
val underMy = map.getAllDescendants("C://path/to/my").toSet()
assertEquals(setOf(10, 20, 30, 10, 43), underMy)
val underMyDir = map.getAllDescendants("C://path/to/my/").toSet()
assertEquals(setOf(10, 20, 30, 10, 43), underMyDir)
val underTo = map.getAllDescendants("C://path/to").toSet()
assertEquals(setOf(10, 20, 30, 10, 43, 11, 21, 30, 11, 43), underTo)
val underToDir = map.getAllDescendants("C://path/to/").toSet()
assertEquals(setOf(10, 20, 30, 10, 43, 11, 21, 30, 11, 43), underToDir)
val underPath = map.getAllDescendants("C://path").toSet()
assertEquals(setOf(10, 20, 30, 10, 43, 11, 21, 30, 11, 43, 13), underPath)
val underProto = map.getAllDescendants("C:/").toSet()
assertEquals(setOf(10, 20, 30, 10, 43, 11, 21, 30, 11, 43, 13), underProto)
}
@Test
fun `test usage with unix paths`() {
val map = PathPrefixTreeMapImpl<Int?>()
map["/path/to/my/dir1/"] = 10
map["/path/to/my/dir2"] = 20
map["/path/to/my/dir3/"] = 30
assertEquals(10, map.remove("/path/to/my/dir1/"))
assertEquals(null, map.remove("/path/to/my/dir1/"))
map["/path/to/my/dir4"] = 10
map["/path/to/dir1"] = 11
map["/path/to/dir2/"] = 21
assertEquals(11, map.remove("/path/to/dir1/"))
map["/path/to/dir3"] = 30
assertEquals(10, map.remove("/path/to/my/dir4/"))
map["/path/to/dir4/"] = 11
assertEquals(null, map.remove("/path"))
map["/path/to/my"] = 43
map["/path"] = 13
assertEquals(13, map.remove("/path"))
assertFalse(map.contains("/path/to/my/dir1"))
assertTrue(map.contains("/path/to/my/dir2"))
assertTrue(map.contains("/path/to/my/dir3"))
assertFalse(map.contains("/path/to/my/dir4/"))
assertFalse(map.contains("/path/to/dir1"))
assertTrue(map.contains("/path/to/dir2/"))
assertTrue(map.contains("/path/to/dir3"))
assertTrue(map.contains("/path/to/dir4/"))
assertTrue(map.contains("/path/to/my"))
assertFalse(map.contains("/path"))
}
}
@@ -417,7 +417,6 @@ public class GradleSettingsImportingTest extends GradleImportingTestCase {
@Test
public void testRemovingSourceFolderManagerMemLeaking() throws IOException {
refreshRecursively(myProjectRoot);
SourceFolderManagerImpl sourceFolderManager = (SourceFolderManagerImpl)SourceFolderManager.getInstance(myProject);
String javaSourcePath = FileUtil.toCanonicalPath(myProjectRoot.getPath() + "/java");
String javaSourceUrl = VfsUtilCore.pathToUrl(javaSourcePath);
@@ -464,9 +463,6 @@ public class GradleSettingsImportingTest extends GradleImportingTestCase {
@Ignore // Remove after published plugin ext with package prefix configuration
@Test
public void testPostponedImportPackagePrefix() throws IOException {
refreshRecursively(myProjectRoot);
ApplicationManager.getApplication().invokeAndWait(() -> {
});
createProjectSubFile("src/main/java/Main.java", "");
importProject(
new GradleBuildScriptBuilderEx()
@@ -487,9 +483,6 @@ public class GradleSettingsImportingTest extends GradleImportingTestCase {
assertSourceNotExists("project.main", "src/main/kotlin");
assertSourceNotExists("project.test", "src/test/java");
createProjectSubFile("src/main/kotlin/Main.kt", "");
refreshRecursively(myProjectRoot);
ApplicationManager.getApplication().invokeAndWait(() -> {
});
assertSourcePackagePrefix("project.main", "src/main/java", "prefix.package.some");
assertSourcePackagePrefix("project.main", "src/main/kotlin", "prefix.package.other");
assertSourceNotExists("project.test", "src/test/java");