Skip to content

Commit

Permalink
Support boolean parameters (#52)
Browse files Browse the repository at this point in the history
Co-authored-by: Jesse Wilson <[email protected]>
  • Loading branch information
swankjesse and squarejesse authored Oct 29, 2024
1 parent db602cb commit 737ab7f
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 24 deletions.
43 changes: 25 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,29 @@ fun drinkSoda(
}
```

### Enum Parameters

If your parameter is an enum type, you don't need to call `burstValues()`. Burst will test each
value of that enum.
### Boolean Parameters

```kotlin
enum class Distribution {
Fountain, Can, Bottle
}
```
If your parameter is a boolean, you don't need to call `burstValues()`. Burst will test both values.
Specify a default value to use that in the IDE.

```kotlin
@Burst
class DrinkSodaTest(
val distribution: Distribution,
val ice: Boolean = true,
) {
...
}
```

If you specify a default value for the enum, Burst will use that when running in the IDE.
### Enum Parameters

Burst will test each value of enum types. Specify a default value to use that in the IDE.

```kotlin
enum class Distribution {
Fountain, Can, Bottle
}

@Burst
class DrinkSodaTest(
val distribution: Distribution = Distribution.Can,
Expand All @@ -78,20 +78,27 @@ Use multiple parameters to test all variations.
@Test
fun drinkSoda(
soda: String = burstValues("Pepsi", "Coke"),
distribution: Distribution,
ice: Boolean = true,
distribution: Distribution = Distribution.Can,
) {
...
}
```

The test will be specialized for each combination of arguments.

* `drinkSoda("Pepsi", Distribution.Fountain)`
* `drinkSoda("Pepsi", Distribution.Can)`
* `drinkSoda("Pepsi", Distribution.Bottle)`
* `drinkSoda("Coke", Distribution.Fountain)`
* `drinkSoda("Coke", Distribution.Can)`
* `drinkSoda("Coke", Distribution.Bottle)`
* `drinkSoda("Pepsi", true, Distribution.Fountain)`
* `drinkSoda("Pepsi", true, Distribution.Can)`
* `drinkSoda("Pepsi", true, Distribution.Bottle)`
* `drinkSoda("Pepsi", false, Distribution.Fountain)`
* `drinkSoda("Pepsi", false, Distribution.Can)`
* `drinkSoda("Pepsi", false, Distribution.Bottle)`
* `drinkSoda("Coke", true, Distribution.Fountain)`
* `drinkSoda("Coke", true, Distribution.Can)`
* `drinkSoda("Coke", true, Distribution.Bottle)`
* `drinkSoda("Coke", false, Distribution.Fountain)`
* `drinkSoda("Coke", false, Distribution.Can)`
* `drinkSoda("Coke", false, Distribution.Bottle)`

Gradle Setup
------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class BurstKotlinPluginTest {
assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode, result.messages)
assertThat(result.messages).contains(
"CoffeeTest.kt:7:12 " +
"@Burst parameter must be an enum or have a burstValues() default value",
"@Burst parameter must be a boolean, enum, or have a burstValues() default value",
)
}

Expand Down Expand Up @@ -134,7 +134,7 @@ class BurstKotlinPluginTest {
assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode, result.messages)
assertThat(result.messages).contains(
"CoffeeTest.kt:9:12 " +
"@Burst parameter default value must be burstValues(), an enum constant, or absent",
"@Burst parameter default value must be burstValues(), a constant, or absent",
)
}

Expand Down Expand Up @@ -573,6 +573,88 @@ class BurstKotlinPluginTest {
)
}

@Test
fun booleanParameters() {
val result = compile(
sourceFile = SourceFile.kotlin(
"CoffeeTest.kt",
"""
import app.cash.burst.Burst
import app.cash.burst.burstValues
import kotlin.test.Test
@Burst
class CoffeeTest {
val log = mutableListOf<String>()
@Test
fun test(iced: Boolean) {
log += "running iced=${'$'}iced"
}
}
""",
),
)
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode, result.messages)

val baseClass = result.classLoader.loadClass("CoffeeTest")
val baseInstance = baseClass.constructors.single().newInstance()
val baseLog = baseClass.getMethod("getLog").invoke(baseInstance) as MutableList<*>

baseClass.getMethod("test_false").invoke(baseInstance)
assertThat(baseLog).containsExactly("running iced=false")
baseLog.clear()

baseClass.getMethod("test_true").invoke(baseInstance)
assertThat(baseLog).containsExactly("running iced=true")
baseLog.clear()
}

@Test
fun booleanDefaultValues() {
val result = compile(
sourceFile = SourceFile.kotlin(
"CoffeeTest.kt",
"""
import app.cash.burst.Burst
import app.cash.burst.burstValues
import kotlin.test.Test
@Burst
class CoffeeTest {
val log = mutableListOf<String>()
@Test
fun testDefaultTrue(iced: Boolean = true) {
log += "running testDefaultTrue iced=${'$'}iced"
}
@Test
fun testDefaultFalse(iced: Boolean = false) {
log += "running testDefaultFalse iced=${'$'}iced"
}
}
""",
),
)
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode, result.messages)

val baseClass = result.classLoader.loadClass("CoffeeTest")
val baseInstance = baseClass.constructors.single().newInstance()
val baseLog = baseClass.getMethod("getLog").invoke(baseInstance) as MutableList<*>

baseClass.getMethod("testDefaultTrue").invoke(baseInstance)
baseClass.getMethod("testDefaultTrue_false").invoke(baseInstance)
baseClass.getMethod("testDefaultFalse").invoke(baseInstance)
baseClass.getMethod("testDefaultFalse_true").invoke(baseInstance)
assertThat(baseLog).containsExactly(
"running testDefaultTrue iced=true",
"running testDefaultTrue iced=false",
"running testDefaultFalse iced=false",
"running testDefaultFalse iced=true",
)
}

private val Class<*>.testSuffixes: List<String>
get() = methods.mapNotNull {
when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetEnumValue
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.classId
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isEnumClass
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.NameUtils
Expand Down Expand Up @@ -69,6 +71,22 @@ private class EnumValueArgument(
}
}

private class BooleanArgument(
private val original: IrElement,
private val booleanType: IrType,
override val isDefault: Boolean,
private val value: Boolean,
) : Argument {
override val name = value.toString()

override fun expression() =
IrConstImpl.boolean(original.startOffset, original.endOffset, booleanType, value)

override fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R {
return original.accept(visitor, data)
}
}

private class BurstValuesArgument(
private val declarationParent: IrDeclarationParent,
override val isDefault: Boolean,
Expand Down Expand Up @@ -104,8 +122,9 @@ internal fun IrPluginContext.allPossibleArguments(

val classId = parameter.type.getClass()?.classId ?: unexpectedParameter(parameter)
val referenceClass = referenceClass(classId)?.owner ?: unexpectedParameter(parameter)
if (referenceClass.isEnumClass) {
return enumValueArguments(referenceClass, parameter)
when {
referenceClass.isEnumClass -> return enumValueArguments(referenceClass, parameter)
referenceClass.defaultType == irBuiltIns.booleanType -> return booleanArguments(parameter)
}

unexpectedParameter(parameter)
Expand Down Expand Up @@ -190,16 +209,33 @@ private fun enumValueArguments(
}
}

private fun IrPluginContext.booleanArguments(
parameter: IrValueParameter,
): List<BooleanArgument> {
val defaultValue = parameter.defaultValue?.let { defaultValue ->
(defaultValue.expression as? IrConst<*>)?.value ?: unexpectedDefaultValue(parameter)
}

return listOf(false, true).map {
BooleanArgument(
original = parameter,
booleanType = irBuiltIns.booleanType,
isDefault = defaultValue == it,
value = it,
)
}
}

private fun unexpectedParameter(parameter: IrValueParameter): Nothing {
throw BurstCompilationException(
"@Burst parameter must be an enum or have a burstValues() default value",
"@Burst parameter must be a boolean, enum, or have a burstValues() default value",
parameter,
)
}

private fun unexpectedDefaultValue(parameter: IrValueParameter): Nothing {
throw BurstCompilationException(
"@Burst parameter default value must be burstValues(), an enum constant, or absent",
"@Burst parameter default value must be burstValues(), a constant, or absent",
parameter,
)
}

0 comments on commit 737ab7f

Please sign in to comment.