[Commonizer] Implement associative commonization

^KT-47301 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-06-11 15:19:35 +02:00
committed by Space
parent 5fdbcb3dd1
commit 42f60d981f
58 changed files with 957 additions and 1091 deletions

View File

@@ -26,7 +26,7 @@ public class CliCommonizer(private val executor: Executor) : Commonizer {
konanHome: File,
inputLibraries: Set<File>,
dependencyLibraries: Set<CommonizerDependency>,
outputCommonizerTarget: SharedCommonizerTarget,
outputTargets: Set<SharedCommonizerTarget>,
outputDirectory: File,
logLevel: CommonizerLogLevel
) {
@@ -35,7 +35,7 @@ public class CliCommonizer(private val executor: Executor) : Commonizer {
add("native-klib-commonize")
add("-distribution-path"); add(konanHome.absolutePath)
add("-input-libraries"); add(inputLibraries.joinToString(";") { it.absolutePath })
add("-output-commonizer-target"); add(outputCommonizerTarget.identityString)
add("-output-targets"); add(outputTargets.joinToString(";") { it.identityString })
add("-output-path"); add(outputDirectory.absolutePath)
if (dependencyLibraries.isNotEmpty()) {
add("-dependency-libraries"); add(dependencyLibraries.joinToString(";"))
@@ -44,6 +44,23 @@ public class CliCommonizer(private val executor: Executor) : Commonizer {
}
executor(arguments)
}
override fun commonizeNativeDistribution(
konanHome: File,
outputDirectory: File,
outputTargets: Set<SharedCommonizerTarget>,
logLevel: CommonizerLogLevel
) {
val arguments = mutableListOf<String>().apply {
add("native-dist-commonize")
add("-distribution-path"); add(konanHome.absolutePath)
add("-output-path"); add(outputDirectory.absolutePath)
add("-output-targets"); add(outputTargets.joinToString(";") { it.identityString })
add("-log-level"); add(logLevel.name.lowercase())
}
executor(arguments)
}
}
private class CommonizerClassLoaderExecutor(private val commonizerClassLoader: ClassLoader) : CliCommonizer.Executor {

View File

@@ -9,13 +9,22 @@ import java.io.File
import java.io.Serializable
public interface Commonizer : Serializable {
@Throws(Throwable::class)
public fun commonizeLibraries(
konanHome: File,
inputLibraries: Set<File>,
dependencyLibraries: Set<CommonizerDependency>,
outputCommonizerTarget: SharedCommonizerTarget,
outputTargets: Set<SharedCommonizerTarget>,
outputDirectory: File,
logLevel: CommonizerLogLevel = CommonizerLogLevel.Quiet
)
@Throws(Throwable::class)
public fun commonizeNativeDistribution(
konanHome: File,
outputDirectory: File,
outputTargets: Set<SharedCommonizerTarget>,
logLevel: CommonizerLogLevel = CommonizerLogLevel.Quiet
)
}

View File

@@ -7,14 +7,13 @@
package org.jetbrains.kotlin.commonizer
import org.jetbrains.kotlin.commonizer.util.transitiveClosure
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.Serializable
// N.B. TargetPlatform/SimplePlatform are non exhaustive enough to address both target platforms such as
// JVM, JS and concrete Kotlin/Native targets, e.g. macos_x64, ios_x64, linux_x64.
public sealed class CommonizerTarget : Serializable {
final override fun toString(): String = prettyName
final override fun toString(): String = identityString
}
public data class LeafCommonizerTarget public constructor(val name: String) : CommonizerTarget() {
@@ -25,16 +24,10 @@ public data class LeafCommonizerTarget public constructor(val name: String) : Co
public val konanTarget: KonanTarget get() = konanTargetOrNull ?: error("Unknown KonanTarget: $name")
}
public data class SharedCommonizerTarget(val targets: Set<CommonizerTarget>) : CommonizerTarget() {
public constructor(vararg targets: CommonizerTarget) : this(targets.toSet())
public data class SharedCommonizerTarget(val targets: Set<LeafCommonizerTarget>) : CommonizerTarget() {
public constructor(vararg targets: LeafCommonizerTarget) : this(targets.toSet())
public constructor(vararg targets: KonanTarget) : this(targets.toSet())
public constructor(targets: Iterable<KonanTarget>) : this(targets.map(::LeafCommonizerTarget).toSet())
public companion object {
public fun ifNotEmpty(targets: Set<CommonizerTarget>): SharedCommonizerTarget? {
return if (targets.isNotEmpty()) SharedCommonizerTarget(targets) else null
}
}
}
public fun CommonizerTarget(konanTargets: Iterable<KonanTarget>): CommonizerTarget {
@@ -56,8 +49,11 @@ public fun CommonizerTarget(konanTarget: KonanTarget, vararg konanTargets: Konan
return SharedCommonizerTarget(targets.map(::LeafCommonizerTarget).toSet())
}
public fun CommonizerTarget(commonizerTarget: CommonizerTarget, vararg commonizerTargets: CommonizerTarget): SharedCommonizerTarget {
val targets = mutableListOf<CommonizerTarget>().apply {
public fun CommonizerTarget(
commonizerTarget: LeafCommonizerTarget,
vararg commonizerTargets: LeafCommonizerTarget
): SharedCommonizerTarget {
val targets = mutableListOf<LeafCommonizerTarget>().apply {
add(commonizerTarget)
addAll(commonizerTargets)
}
@@ -78,22 +74,6 @@ private val SharedCommonizerTarget.identityString: String
)
}
public val CommonizerTarget.prettyName: String
get() = when (this) {
is LeafCommonizerTarget -> "[$name]"
is SharedCommonizerTarget -> prettyName(null)
}
public fun SharedCommonizerTarget.prettyName(highlightedChild: CommonizerTarget?): String {
return targets
.sortedWith(compareBy<CommonizerTarget> { it.level }.thenBy { it.identityString }).joinToString(", ", "[", "]") { child ->
when (child) {
is LeafCommonizerTarget -> child.name
is SharedCommonizerTarget -> child.prettyName(highlightedChild)
} + if (child == highlightedChild) "(*)" else ""
}
}
public val CommonizerTarget.konanTargets: Set<KonanTarget>
get() {
return when (this) {
@@ -102,6 +82,9 @@ public val CommonizerTarget.konanTargets: Set<KonanTarget>
}
}
public val Iterable<CommonizerTarget>.konanTargets: Set<KonanTarget> get() = flatMapTo(mutableSetOf()) { it.konanTargets }
// REMOVE
public val CommonizerTarget.level: Int
get() {
return when (this) {
@@ -110,30 +93,25 @@ public val CommonizerTarget.level: Int
}
}
public fun CommonizerTarget.withAllAncestors(): Set<CommonizerTarget> {
return setOf(this) + transitiveClosure(this) {
when (this) {
is SharedCommonizerTarget -> targets
is LeafCommonizerTarget -> emptyList()
}
}
}
public fun CommonizerTarget.allLeaves(): Set<LeafCommonizerTarget> {
return withAllAncestors().filterIsInstance<LeafCommonizerTarget>().toSet()
}
public infix fun CommonizerTarget.isAncestorOf(other: CommonizerTarget): Boolean {
if (this is SharedCommonizerTarget) {
return targets.any { it == other } || targets.any { it.isAncestorOf(other) }
return when (this) {
is LeafCommonizerTarget -> setOf(this)
is SharedCommonizerTarget -> this.targets
}
return false
}
public infix fun CommonizerTarget.isEqualOrAncestorOf(other: CommonizerTarget): Boolean {
return this == other || this.isAncestorOf(other)
public fun Iterable<CommonizerTarget>.allLeaves(): Set<LeafCommonizerTarget> {
return flatMapTo(mutableSetOf()) { target -> target.allLeaves() }
}
public infix fun CommonizerTarget.isDescendentOf(other: CommonizerTarget): Boolean {
return other.isAncestorOf(this)
public fun CommonizerTarget.withAllLeaves(): Set<CommonizerTarget> {
return when (this) {
is LeafCommonizerTarget -> setOf(this)
is SharedCommonizerTarget -> setOf(this) + targets
}
}
public fun Iterable<CommonizerTarget>.withAllLeaves(): Set<CommonizerTarget> {
return flatMapTo(mutableSetOf()) { it.withAllLeaves() }
}

View File

@@ -5,30 +5,14 @@
package org.jetbrains.kotlin.commonizer
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_COMMON_LIBS_DIR
import org.jetbrains.kotlin.konan.library.KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR
import java.io.File
import java.security.MessageDigest
import java.util.*
public fun interface CommonizerOutputLayout {
public fun getTargetDirectory(root: File, target: CommonizerTarget): File
}
public object NativeDistributionCommonizerOutputLayout : CommonizerOutputLayout {
override fun getTargetDirectory(root: File, target: CommonizerTarget): File {
return when (target) {
is LeafCommonizerTarget -> root.resolve(KONAN_DISTRIBUTION_PLATFORM_LIBS_DIR).resolve(target.name)
is SharedCommonizerTarget -> root.resolve(KONAN_DISTRIBUTION_COMMON_LIBS_DIR)
}
}
}
public object HierarchicalCommonizerOutputLayout : CommonizerOutputLayout {
public object CommonizerOutputFileLayout {
internal const val maxFileNameLength = 150
override fun getTargetDirectory(root: File, target: CommonizerTarget): File {
public fun getCommonizedDirectory(root: File, target: CommonizerTarget): File {
return root.resolve(target.fileName)
}
@@ -42,11 +26,18 @@ public object HierarchicalCommonizerOutputLayout : CommonizerOutputLayout {
}
}
public val Set<CommonizerTarget>.fileName: String
get() = this.joinToString(";") { it.identityString }.base64Hash
private val CommonizerTarget.identityStringHash: String
get() = identityString.base64Hash
private val String.base64Hash: String
get() {
val sha = MessageDigest.getInstance("SHA-1")
val base64 = Base64.getUrlEncoder()
return base64.encode(sha.digest(identityString.encodeToByteArray())).decodeToString()
return base64.encode(sha.digest(this.encodeToByteArray())).decodeToString()
}
}

View File

@@ -192,8 +192,9 @@ private sealed class IdentityStringSyntaxNode {
private fun buildCommonizerTarget(node: IdentityStringSyntaxNode): CommonizerTarget {
return when (node) {
is LeafTargetSyntaxNode -> LeafCommonizerTarget(node.token.value)
// Previous nested ((a, b), c) notation is still valid and will be flattened to (a, b, c)
is SharedTargetSyntaxNode -> SharedCommonizerTarget(
node.children.map { child -> buildCommonizerTarget(child) }.toSet()
node.children.flatMap { child -> buildCommonizerTarget(child).allLeaves() }.toSet()
)
}
}

View File

@@ -23,7 +23,7 @@ class CliCommonizerTest {
konanHome = konanHome,
inputLibraries = emptySet(),
dependencyLibraries = emptySet(),
outputCommonizerTarget = CommonizerTarget(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64),
outputTargets = setOf(CommonizerTarget(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64)),
outputDirectory = temporaryOutputDirectory.root
)
}

View File

@@ -34,24 +34,13 @@ class CommonizeLibcurlTest {
KonanDistribution(konanHome).platformLibsDir.resolve(LINUX_ARM64.name).listFiles().orEmpty()
.map { TargetedCommonizerDependency(LeafCommonizerTarget(LINUX_ARM64), it) }
.toSet(),
outputCommonizerTarget = CommonizerTarget(LINUX_ARM64, LINUX_X64),
outputDirectory = temporaryOutputDirectory.root
outputTargets = setOf(CommonizerTarget(LINUX_ARM64, LINUX_X64)),
outputDirectory = temporaryOutputDirectory.root,
logLevel = CommonizerLogLevel.Info
)
val x64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_X64).identityString)
val arm64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_ARM64).identityString)
val commonOutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_X64, LINUX_ARM64).identityString)
assertTrue(
x64OutputDirectory.exists(),
"Missing output directory for x64 target"
)
assertTrue(
arm64OutputDirectory.exists(),
"Missing output directory for arm64 target"
)
assertTrue(
commonOutputDirectory.exists(),
"Missing output directory for commonized x64&arm64 target"
@@ -64,18 +53,8 @@ class CommonizeLibcurlTest {
)
}
assertContainsKnmFiles(x64OutputDirectory)
assertContainsKnmFiles(arm64OutputDirectory)
assertContainsKnmFiles(commonOutputDirectory)
assertContainsManifestWithContent(x64OutputDirectory, "native_targets=linux_x64")
assertContainsManifestWithContent(arm64OutputDirectory, "native_targets=linux_arm64")
assertContainsManifestWithContent(commonOutputDirectory, "native_targets=linux_arm64 linux_x64")
assertContainsManifestWithContent(x64OutputDirectory, "commonizer_target=linux_x64")
assertContainsManifestWithContent(arm64OutputDirectory, "commonizer_target=linux_arm64")
assertContainsManifestWithContent(commonOutputDirectory, "commonizer_native_targets=linux_arm64 linux_x64")
assertContainsManifestWithContent(
commonOutputDirectory, "commonizer_target=${CommonizerTarget(LINUX_X64, LINUX_ARM64).identityString}"
@@ -98,22 +77,14 @@ class CommonizeLibcurlTest {
KonanDistribution(konanHome).platformLibsDir.resolve(LINUX_ARM64.name).listFiles().orEmpty()
.map { TargetedCommonizerDependency(LeafCommonizerTarget(LINUX_ARM64), it) }
.toSet(),
outputCommonizerTarget = CommonizerTarget(LINUX_ARM64, LINUX_X64, MACOS_X64),
outputTargets = setOf(CommonizerTarget(LINUX_ARM64, LINUX_X64, MACOS_X64)),
outputDirectory = temporaryOutputDirectory.root
)
val x64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_X64).identityString)
val arm64OutputDirectory = temporaryOutputDirectory.root.resolve(CommonizerTarget(LINUX_ARM64).identityString)
val commonOutputDirectory = temporaryOutputDirectory.root
.resolve(CommonizerTarget(LINUX_X64, LINUX_ARM64, MACOS_X64).identityString)
assertContainsManifestWithContent(x64OutputDirectory, "native_targets=linux_x64")
assertContainsManifestWithContent(arm64OutputDirectory, "native_targets=linux_arm64")
assertContainsManifestWithContent(commonOutputDirectory, "native_targets=linux_arm64 linux_x64")
assertContainsManifestWithContent(x64OutputDirectory, "commonizer_target=linux_x64")
assertContainsManifestWithContent(arm64OutputDirectory, "commonizer_target=linux_arm64")
assertContainsManifestWithContent(commonOutputDirectory, "commonizer_native_targets=linux_arm64 linux_x64 macos_x64")
assertContainsManifestWithContent(
commonOutputDirectory, "commonizer_target=${CommonizerTarget(LINUX_X64, LINUX_ARM64, MACOS_X64).identityString}"

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2010-2021 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.commonizer
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.getCommonizedDirectory
import org.jetbrains.kotlin.commonizer.utils.konanHome
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertTrue
class CommonizeNativeDistributionTest {
@get:Rule
val temporaryOutputDirectory = TemporaryFolder()
@Test
fun commonizeLinuxPlatforms() {
val linuxTarget1 = CommonizerTarget(LINUX_X64, LINUX_ARM64)
val linuxTarget2 = CommonizerTarget(LINUX_X64, LINUX_ARM64, LINUX_ARM32_HFP)
CliCommonizer(this::class.java.classLoader).commonizeNativeDistribution(
konanHome = konanHome,
outputTargets = setOf(linuxTarget1, linuxTarget2),
outputDirectory = temporaryOutputDirectory.root,
logLevel = CommonizerLogLevel.Info
)
assertTrue(
getCommonizedDirectory(temporaryOutputDirectory.root, linuxTarget1).isDirectory,
"Expected directory for $linuxTarget1"
)
assertTrue(
getCommonizedDirectory(temporaryOutputDirectory.root, linuxTarget2).isDirectory,
"Expected directory for $linuxTarget2"
)
}
}

View File

@@ -14,8 +14,8 @@ class CommonizerDependencyTest {
@Test
fun `sample identityString`() {
assertEquals(
"((a, b), c)::${File("/").canonicalPath}hello.txt",
TargetedCommonizerDependency(parseCommonizerTarget("((a,b), c)"), File("/hello.txt")).identityString
"(a, b, c)::${File("/").canonicalPath}hello.txt",
TargetedCommonizerDependency(parseCommonizerTarget("(a, b, c)"), File("/hello.txt")).identityString
)
}

View File

@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.commonizer
import org.jetbrains.kotlin.commonizer.HierarchicalCommonizerOutputLayout.fileName
import org.jetbrains.kotlin.commonizer.HierarchicalCommonizerOutputLayout.maxFileNameLength
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.fileName
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.maxFileNameLength
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import kotlin.test.Test

View File

@@ -33,60 +33,14 @@ class CommonizerTargetIdentityStringTest {
}
@Test
fun `hierarchical commonizer targets`() {
val hierarchy = SharedCommonizerTarget(
CommonizerTarget(LINUX_X64, MACOS_X64),
CommonizerTarget(IOS_ARM64, IOS_X64)
)
assertEquals(setOf(LINUX_X64, MACOS_X64, IOS_ARM64, IOS_X64), hierarchy.konanTargets)
assertEquals(hierarchy, parseCommonizerTarget(hierarchy.identityString))
}
@Test
fun `multilevel hierarchical commonizer targets`() {
val hierarchy = SharedCommonizerTarget(
SharedCommonizerTarget(
SharedCommonizerTarget(
SharedCommonizerTarget(
CommonizerTarget(LINUX_X64, MACOS_X64),
CommonizerTarget(IOS_X64, IOS_ARM64)
),
CommonizerTarget(LINUX_ARM32_HFP)
),
CommonizerTarget(LINUX_MIPSEL32)
),
CommonizerTarget(WATCHOS_X86, WATCHOS_ARM64)
)
assertEquals(hierarchy, parseCommonizerTarget(hierarchy.identityString))
}
@Test
fun `parsing CommonizerTarget`() {
fun `parsing CommonizerTarget with 1 5 20 notation`() {
val target = parseCommonizerTarget("(x, (x, y, (a, b), (b, c)))")
assertEquals(
SharedCommonizerTarget(
LeafCommonizerTarget("x"),
SharedCommonizerTarget(
LeafCommonizerTarget("x"),
LeafCommonizerTarget("y"),
SharedCommonizerTarget(
LeafCommonizerTarget("a"),
LeafCommonizerTarget("b"),
),
SharedCommonizerTarget(
LeafCommonizerTarget("b"),
LeafCommonizerTarget("c")
)
)
),
target
)
assertEquals(SharedCommonizerTarget(setOf("x", "y", "a", "b", "c").map(::LeafCommonizerTarget).toSet()), target)
}
@Test
fun `empty shared target`() {
assertEquals(SharedCommonizerTarget(emptySet<CommonizerTarget>()), parseCommonizerTarget("()"))
assertEquals(SharedCommonizerTarget(emptySet<LeafCommonizerTarget>()), parseCommonizerTarget("()"))
}
@Test(expected = IllegalArgumentException::class)

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2010-2021 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.commonizer
import org.junit.Test
import kotlin.test.assertEquals
class CommonizerTargetPrettyNameTest {
@Test
fun leafTargetNames() {
listOf(
Triple("foo", "[foo]", FOO),
Triple("bar", "[bar]", BAR),
Triple("baz_123", "[baz_123]", BAZ),
).forEach { (name, prettyName, target: LeafCommonizerTarget) ->
assertEquals(name, target.name)
assertEquals(prettyName, target.prettyName)
}
}
@Test
fun sharedTargetNames() {
listOf(
"[foo]" to SharedTarget(FOO),
"[bar, foo]" to SharedTarget(FOO, BAR),
"[bar, baz_123, foo]" to SharedTarget(FOO, BAR, BAZ),
"[bar, baz_123, foo, [bar, foo]]" to SharedTarget(FOO, BAR, BAZ, SharedTarget(FOO, BAR))
).forEach { (prettyName, target: SharedCommonizerTarget) ->
assertEquals(prettyName, target.prettyName)
}
}
@Test
fun prettyCommonizedName() {
val sharedTarget = SharedTarget(FOO, BAR, BAZ)
listOf(
"[bar, baz_123, foo(*)]" to FOO,
"[bar(*), baz_123, foo]" to BAR,
"[bar, baz_123(*), foo]" to BAZ,
"[bar, baz_123, foo]" to sharedTarget,
).forEach { (prettyCommonizerName, target: CommonizerTarget) ->
assertEquals(prettyCommonizerName, sharedTarget.prettyName(target))
}
}
@Test
fun prettyNestedName() {
val target = parseCommonizerTarget("(a, b, (c, (d, e)))") as SharedCommonizerTarget
assertEquals(
"[a, b, [c, [d, e]]]", target.prettyName
)
assertEquals(
"[a, b, [c, [d, e(*)]]]", target.prettyName(LeafCommonizerTarget("e"))
)
assertEquals(
"[a, b, [c, [d, e](*)]]", target.prettyName(parseCommonizerTarget("(d, e)"))
)
assertEquals(
"[a, b, [c, [d, e]](*)]", target.prettyName(parseCommonizerTarget("(c, (d, e))"))
)
assertEquals(
"[a, b(*), [c, [d, e]]]", target.prettyName(LeafCommonizerTarget("b"))
)
}
@Test
fun sharedTargetNoInnerTargets() {
assertEquals(
"[]", SharedCommonizerTarget(emptySet<CommonizerTarget>()).prettyName
)
}
private companion object {
val FOO = LeafCommonizerTarget("foo")
val BAR = LeafCommonizerTarget("bar")
val BAZ = LeafCommonizerTarget("baz_123")
@Suppress("TestFunctionName")
fun SharedTarget(vararg targets: CommonizerTarget) = SharedCommonizerTarget(linkedSetOf(*targets))
}
}

View File

@@ -7,53 +7,9 @@ package org.jetbrains.kotlin.commonizer
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class CommonizerTargetUtilsTest {
@Test
fun `isAncestorOf isDescendentOf`() {
val child = LeafCommonizerTarget("a")
val parent = SharedCommonizerTarget(LeafCommonizerTarget("a"))
assertTrue(child isDescendentOf parent, "Expected child isDescendent of parent")
assertTrue(parent isAncestorOf child, "Expected parent isAncestor of child")
assertFalse(child isDescendentOf child, "Expected same target not being descendent of itself")
assertFalse(parent isDescendentOf parent, "Expected same target not being descendent of itself")
assertFalse(LeafCommonizerTarget("b") isDescendentOf parent, "Expected orphan target not being descendent of parent")
val hierarchicalParent = SharedCommonizerTarget(parent, parseCommonizerTarget("((c, d), e)"))
assertTrue(child isDescendentOf hierarchicalParent, "Expected child being descendent of hierarchical parent")
assertTrue(hierarchicalParent isAncestorOf child, "Expected hierarchicalParent being ancestor of child")
assertTrue(parseCommonizerTarget("(c, d)") isDescendentOf hierarchicalParent)
assertTrue(LeafCommonizerTarget("e") isDescendentOf hierarchicalParent)
}
@Test
fun withAllAncestors() {
val target = parseCommonizerTarget("((a, b), (c, d), (e, (f, g)))")
assertEquals(
setOf(
target,
parseCommonizerTarget("(a, b)"),
parseCommonizerTarget("(c, d)"),
parseCommonizerTarget("(e, (f, g))"),
parseCommonizerTarget("(f, g)"),
LeafCommonizerTarget("a"),
LeafCommonizerTarget("b"),
LeafCommonizerTarget("c"),
LeafCommonizerTarget("d"),
LeafCommonizerTarget("e"),
LeafCommonizerTarget("f"),
LeafCommonizerTarget("g")
),
target.withAllAncestors(),
"Expected all targets present"
)
}
@Test
fun allLeaves() {
val target = parseCommonizerTarget("((a, b), (c, d), (e, (f, g)))")
@@ -76,5 +32,18 @@ class CommonizerTargetUtilsTest {
"Expected LeafCommonizerTarget returns itself in 'allLeaves'"
)
}
@Test
fun `withAllLeaves LeafCommonizerTarget`() {
assertEquals(setOf(LeafCommonizerTarget("a")), LeafCommonizerTarget("a").withAllLeaves())
}
@Test
fun `withAllLeaves SharedCommonizerTarget`() {
assertEquals(
setOf(parseCommonizerTarget("(a, b)"), LeafCommonizerTarget("a"), LeafCommonizerTarget("b")),
parseCommonizerTarget("(a, b)").withAllLeaves()
)
}
}