Skip to content

Commit

Permalink
Cover CLI by tests
Browse files Browse the repository at this point in the history
  • Loading branch information
egorikftp committed Nov 20, 2024
1 parent c89cea0 commit 7433f38
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 20 deletions.
24 changes: 19 additions & 5 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ plugins {
val baseName = "valkyrie"
val versionName = rootProject.providers.gradleProperty("VERSION_NAME").get()

application {
mainClass = "io.github.composegears.valkyrie.cli.MainKt"
applicationName = "valkyrie"
version = versionName
}

sourceSets {
test {
resources {
srcDir("$rootDir/components/sharedTestResources")
}
}
}

buildConfig {
buildConfigField("VERSION_NAME", versionName)
packageName = "io.github.composegears.valkyrie.cli"
Expand All @@ -22,11 +36,6 @@ tasks.withType<Jar>().configureEach {
attributes["Implementation-Version"] = versionName
}
}
application {
mainClass = "io.github.composegears.valkyrie.cli.MainKt"
applicationName = "valkyrie"
version = versionName
}

val buildWithR8 by tasks.registering(JavaExec::class) {
dependsOn(tasks.installShadowDist)
Expand Down Expand Up @@ -59,6 +68,11 @@ val buildCLI by tasks.registering(Zip::class) {
destinationDirectory.set(layout.buildDirectory.dir("distributions/"))
}

tasks.test {
dependsOn(buildWithR8)
systemProperty("CLI_PATH", layout.buildDirectory.file("install/cli-shadow/bin").get().asFile.path)
}

val r8: Configuration by configurations.creating

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.output.MordantHelpFormatter
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.varargValues
import com.github.ajalt.clikt.parameters.options.split
import io.github.composegears.valkyrie.cli.ext.booleanOption
import io.github.composegears.valkyrie.cli.ext.intOption
import io.github.composegears.valkyrie.cli.ext.outputInfo
Expand Down Expand Up @@ -49,7 +49,7 @@ internal class IconPackCommand : CliktCommand(name = "iconpack") {
private val nestedPacks by option(
"--nested-packs",
help = "Nested packs (e.g. Filled, Colored)",
).varargValues()
).split(",")

private val indentSize by intOption(
"--indent-size",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package io.github.composegears.valkyrie.cli

import assertk.assertThat
import assertk.assertions.isEqualTo
import io.github.composegears.valkyrie.cli.IconPackCommand.IconPackName
import io.github.composegears.valkyrie.cli.IconPackCommand.IndentSize
import io.github.composegears.valkyrie.cli.IconPackCommand.NestedPacks
import io.github.composegears.valkyrie.cli.IconPackCommand.OutputPath
import io.github.composegears.valkyrie.cli.IconPackCommand.PackageName
import io.github.composegears.valkyrie.cli.IconPackCommand.UseExplicitMode
import io.github.composegears.valkyrie.cli.common.CliTestType
import io.github.composegears.valkyrie.cli.common.CliTestType.DirectMain
import io.github.composegears.valkyrie.cli.common.CliTestType.JarTerminal
import io.github.composegears.valkyrie.cli.common.CommandLineTestRunner
import io.github.composegears.valkyrie.extensions.ResourceUtils.getResourceText
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.createTempDirectory
import kotlin.io.path.readText
import kotlin.properties.Delegates
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource

class IconPackCliTest {

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate icon pack`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.kt",
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate icon pack explicit mode`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.explicit.kt",
useExplicitMode = UseExplicitMode(true),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested packs`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested packs explicit`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.explicit.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
useExplicitMode = UseExplicitMode(true),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested indent 1 packs`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.indent1.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
indentSize = IndentSize(1),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested indent 2 packs`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.indent2.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
indentSize = IndentSize(2),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested indent 3 packs`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.indent3.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
indentSize = IndentSize(3),
)
}

@ParameterizedTest
@EnumSource(value = CliTestType::class)
fun `generate nested indent 6 packs`(cliTestType: CliTestType) {
testIconPack(
cliTestType = cliTestType,
expectedResource = "iconpack/IconPack.nested.indent6.kt",
nestedPacks = NestedPacks(listOf("Filled", "Colored")),
indentSize = IndentSize(6),
)
}

private fun testIconPack(
cliTestType: CliTestType,
expectedResource: String,
useExplicitMode: UseExplicitMode? = null,
nestedPacks: NestedPacks? = null,
indentSize: IndentSize? = null,
) {
generateIconPack(
cliTestType = cliTestType,
packCommands = listOfNotNull(
OutputPath(tempDir.absolutePathString()),
PackageName(name = "io.github.composegears.valkyrie.icons"),
IconPackName(name = "ValkyrieIcons"),
useExplicitMode,
nestedPacks,
indentSize,
),
)

val files = tempDir.toFile().listFiles().orEmpty()
assertThat(files.size).isEqualTo(1)

val result = tempDir.resolve("ValkyrieIcons.kt").readText()
val expected = getResourceText(expectedResource)
assertThat(result).isEqualTo(expected)
}

private fun generateIconPack(cliTestType: CliTestType, packCommands: List<IconPackCommand>) {
val commands = listOf("iconpack") + packCommands.map { it.command }

when (cliTestType) {
DirectMain -> main(*commands.toTypedArray())
JarTerminal -> CommandLineTestRunner(commands).run()
}
}

companion object {
// Workaround for https://github.com/junit-team/junit5/issues/2811.
@JvmStatic
private var tempDir: Path by Delegates.notNull()

@JvmStatic
@BeforeAll
fun before() {
tempDir = createTempDirectory()
}

@JvmStatic
@AfterAll
fun after() {
tempDir.toFile().delete()
}
}
}

private sealed interface IconPackCommand {
val command: String

data class OutputPath(val path: String) : IconPackCommand {
override val command: String = "--output-path=$path"
}

data class PackageName(val name: String) : IconPackCommand {
override val command: String = "--package-name=$name"
}

data class IconPackName(val name: String) : IconPackCommand {
override val command: String = "--iconpack-name=$name"
}

data class NestedPacks(val packs: List<String>) : IconPackCommand {
override val command: String = "--nested-packs=${packs.joinToString(separator = ",")}"
}

data class IndentSize(val size: Int) : IconPackCommand {
override val command: String = "--indent-size=$size"
}

data class UseExplicitMode(val use: Boolean) : IconPackCommand {
override val command: String = "--use-explicit-mode=$use"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.composegears.valkyrie.cli.common

enum class CliTestType {
DirectMain,
JarTerminal,
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package io.github.composegears.valkyrie.cli

import io.github.composegears.valkyrie.cli.command.SUCCESS_MESSAGE
package io.github.composegears.valkyrie.cli.common

class CommandLineTestRunner(
private var commands: List<String>,
Expand All @@ -11,28 +9,22 @@ class CommandLineTestRunner(
val exitCode = process.waitFor()

val err = process.errorStream.readBytes().toString(Charsets.UTF_8)
val out = process.inputStream.readBytes().toString(Charsets.UTF_8).let {
// If ANSI escape codes are not supported, remove them from the output
if (System.console() == null) it.replace("\u001B\\[.*?m".toRegex(), "") else it
}

if (exitCode != 0 || err.isNotEmpty()) {
error("Error occurred when running command line: $err")
}
if (!out.startsWith(SUCCESS_MESSAGE)) {
error("Output is not correct: $out")
}
}

companion object {
private val isWindows = System.getProperty("os.name").startsWith("Windows")
private val cliPath = System.getProperty("CLI_PATH") ?: error("CLI_PATH must not be null.")

private fun cliCommand(arguments: List<String>) = buildList {
// Binary Jar is not executable on Windows.
if (isWindows) {
addAll(listOf("java", "-jar"))
add("$cliPath/valkyrie.bat")
} else {
add("$cliPath/valkyrie")
}
add(cliPath)
addAll(arguments)
}
}
Expand Down

0 comments on commit 7433f38

Please sign in to comment.