Repository: bnorm/kotlin-power-assert (kotlin)

Issue:
Fails to compile with K/N 1.4.10, when a message is provided to assert: java.lang.IllegalStateException: Expected IrDeclaration: BLOCK type=kotlin.Any origin=null
```
 * Compiler version info: Konan: 1.4.10 / Kotlin: 1.4.10
 * Output kind: LIBRARY
e: java.lang.IllegalStateException: Expected IrDeclaration: BLOCK type=kotlin.Any origin=null
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrSymbol(IrFileSerializer.kt:287)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeReturn(IrFileSerializer.kt:664)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:935)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:968)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeBlock(IrFileSerializer.kt:445)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:914)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStringConcat(IrFileSerializer.kt:470)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:938)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeReturn(IrFileSerializer.kt:665)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:935)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:968)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeBlockBody(IrFileSerializer.kt:425)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:971)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrStatementBody(IrFileSerializer.kt:162)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrFunctionBase(IrFileSerializer.kt:1043)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrFunction(IrFileSerializer.kt:1055)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeFunctionExpression(IrFileSerializer.kt:530)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:925)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeMemberAccessCommon(IrFileSerializer.kt:501)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeCall(IrFileSerializer.kt:516)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:917)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeBranch(IrFileSerializer.kt:434)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:974)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeWhen(IrFileSerializer.kt:784)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:943)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:968)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeBlock(IrFileSerializer.kt:445)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeExpression(IrFileSerializer.kt:914)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:968)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeBlockBody(IrFileSerializer.kt:425)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeStatement(IrFileSerializer.kt:971)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrStatementBody(IrFileSerializer.kt:162)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrFunctionBase(IrFileSerializer.kt:1043)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrFunction(IrFileSerializer.kt:1055)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeDeclaration(IrFileSerializer.kt:1181)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrClass(IrFileSerializer.kt:1125)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeDeclaration(IrFileSerializer.kt:1189)
	at org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer.serializeIrFile(IrFileSerializer.kt:1240)
	at org.jetbrains.kotlin.backend.common.serialization.IrModuleSerializer.serializeIrFile(IrModuleSerializer.kt:28)
	at org.jetbrains.kotlin.backend.common.serialization.IrModuleSerializer.serializedIrModule(IrModuleSerializer.kt:35)
	at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$serializerPhase$1.invoke(ToplevelPhases.kt:314)
	at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$serializerPhase$1.invoke(ToplevelPhases.kt)
	at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:149)
	at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:147)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper$runBody$1.invoke(CompilerPhase.kt:128)
	at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.downlevel(CompilerPhase.kt:24)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper.runBody(CompilerPhase.kt:127)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper.invoke(CompilerPhase.kt:105)
	at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:30)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper$runBody$1.invoke(CompilerPhase.kt:128)
	at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.downlevel(CompilerPhase.kt:24)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper.runBody(CompilerPhase.kt:127)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedPhaseWrapper.invoke(CompilerPhase.kt:105)
	at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:42)
	at org.jetbrains.kotlin.backend.konan.KonanDriverKt.runTopLevelPhases(KonanDriver.kt:28)
	at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:78)
	at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:35)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:86)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:76)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:45)
	at org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:227)
	at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:276)
	at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:260)
	at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:27)
	at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:21)
	at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithGradleRenderer(K2Native.kt:275)
	at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithGradleRenderer(K2Native.kt:427)
	at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt:39)
	at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt)
	at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:17)
	at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain(main.kt:39)
```

Pull request description:
Fixes #22 
Fixes #23

Patch:
diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt
index 9558934..e5d06ab 100644
--- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt
+++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertCallTransformer.kt
@@ -16,6 +16,7 @@
 
 package com.bnorm.power
 
+import com.bnorm.power.internal.ReturnableBlockTransformer
 import org.jetbrains.kotlin.backend.common.FileLoweringPass
 import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
 import org.jetbrains.kotlin.backend.common.ir.inline
 import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
 import org.jetbrains.kotlin.backend.common.lower.at
+import org.jetbrains.kotlin.cli.common.messages.*
 import org.jetbrains.kotlin.descriptors.Visibilities
 import org.jetbrains.kotlin.ir.IrElement
 import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
@@ -77,6 +79,7 @@ fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) {
 
 class PowerAssertCallTransformer(
   private val context: IrPluginContext,
+  private val messageCollector: MessageCollector,
   private val functions: Set<FqName>
 ) : IrElementTransformerVoidWithContext(), FileLoweringPass {
   private lateinit var file: IrFile
@@ -95,9 +98,15 @@ class PowerAssertCallTransformer(
     if (functions.none { fqName == it })
       return super.visitCall(expression)
 
+    // Find a valid delegate function or do not translate
     val delegate = findDelegate(fqName) ?: run {
-      // Find a valid delegate function or do not translate
-      // TODO log a warning
+      val line = fileSource.substring(expression.startOffset).count { it == '\n' } + 1
+      val location = CompilerMessageLocation.create(file.path, line, -1, null)
+      messageCollector.report(
+        CompilerMessageSeverity.WARNING,
+        "Unable to find overload for function $fqName callable as $fqName(Boolean, String) or $fqName(Boolean, () -> String) for power-assertion transformation",
+        location
+      )
       return super.visitCall(expression)
     }
 
@@ -105,6 +114,19 @@ class PowerAssertCallTransformer(
     val assertionArgument = expression.getValueArgument(0)!!
     val messageArgument = if (function.valueParameters.size == 2) expression.getValueArgument(1) else null
 
+    // If the tree does not contain any children, the expression is not transformable
+    val tree = buildAssertTree(assertionArgument)
+    val root = tree.children.singleOrNull() ?: run {
+      val line = fileSource.substring(expression.startOffset).count { it == '\n' } + 1
+      val location = CompilerMessageLocation.create(file.path, line, -1, null)
+      messageCollector.report(
+        CompilerMessageSeverity.INFO,
+        "Expression is constant and will not be power-assertion transformed",
+        location
+      )
+      return super.visitCall(expression)
+    }
+
     val symbol = currentScope!!.scope.scopeOwnerSymbol
     DeclarationIrBuilder(context, symbol).run {
       at(expression)
@@ -116,7 +138,7 @@ class PowerAssertCallTransformer(
           val title = when {
             messageArgument is IrConst<*> -> messageArgument
             messageArgument is IrStringConcatenation -> messageArgument
-            lambda != null -> lambda.inline(parent)
+            lambda != null -> lambda.inline(parent).transform(ReturnableBlockTransformer(context, symbol), null)
             messageArgument != null -> {
               val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
               irCallOp(invoke.symbol, invoke.returnType, messageArgument)
@@ -129,9 +151,6 @@ class PowerAssertCallTransformer(
         }
       }
 
-      val tree = buildAssertTree(assertionArgument)
-      val root = tree.children.single()
-
 //      println(assertionArgument.dump())
 //      println(tree.dump())
 
@@ -147,7 +166,7 @@ class PowerAssertCallTransformer(
   private fun findDelegate(fqName: FqName): FunctionDelegate? {
     return context.findOverloads(fqName)
       .mapNotNull { overload ->
-        // TODO allow other signatures than (Boolean, String) and (Boolean, () -> String))
+        // TODO allow other signatures than (Boolean, String) and (Boolean, () -> String)
         val parameters = overload.owner.valueParameters
         if (parameters.size != 2) return@mapNotNull null
         if (!parameters[0].type.isBoolean()) return@mapNotNull null
diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertComponentRegistrar.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertComponentRegistrar.kt
index 8e7f37d..1cbd298 100644
--- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertComponentRegistrar.kt
+++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertComponentRegistrar.kt
@@ -18,6 +18,8 @@ package com.bnorm.power
 
 import com.google.auto.service.AutoService
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
 import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
 import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -38,7 +40,9 @@ class PowerAssertComponentRegistrar(
   ) {
     val functions = configuration[KEY_FUNCTIONS]?.map { FqName(it) } ?: functions
     if (functions.isEmpty()) return
-    IrGenerationExtension.registerExtension(project, PowerAssertIrGenerationExtension(functions.toSet()))
+
+    val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
+    IrGenerationExtension.registerExtension(project, PowerAssertIrGenerationExtension(messageCollector, functions.toSet()))
   }
 }
 
diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt
index 85d4c48..3d05f86 100644
--- a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt
+++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/PowerAssertIrGenerationExtension.kt
@@ -18,15 +18,17 @@ package com.bnorm.power
 
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
 import org.jetbrains.kotlin.name.FqName
 
 class PowerAssertIrGenerationExtension(
+  private val messageCollector: MessageCollector,
   private val functions: Set<FqName>
 ) : IrGenerationExtension {
   override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
     for (file in moduleFragment.files) {
-      PowerAssertCallTransformer(pluginContext, functions).runOnFileInOrder(file)
+      PowerAssertCallTransformer(pluginContext, messageCollector, functions).runOnFileInOrder(file)
     }
   }
 }
diff --git a/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/internal/ReturnableBlockTransformer.kt b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/internal/ReturnableBlockTransformer.kt
new file mode 100644
index 0000000..13f85e4
--- /dev/null
+++ b/kotlin-power-assert-plugin/src/main/kotlin/com/bnorm/power/internal/ReturnableBlockTransformer.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010-2018 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.
+ *
+ * Copied from https://github.com/JetBrains/kotlin/blob/1.4.0/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/ReturnableBlockLowering.kt
+ */
+
+package com.bnorm.power.internal
+
+import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
+import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
+import org.jetbrains.kotlin.backend.common.lower.irComposite
+import org.jetbrains.kotlin.ir.builders.*
+import org.jetbrains.kotlin.ir.expressions.*
+import org.jetbrains.kotlin.ir.expressions.impl.*
+import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
+import org.jetbrains.kotlin.ir.symbols.IrSymbol
+
+// TODO Remove when inlining works correctly on Kotlin/JS and Kotlin/Native
+class ReturnableBlockTransformer(val context: IrGeneratorContext, val containerSymbol: IrSymbol? = null) : IrElementTransformerVoidWithContext() {
+  private var labelCnt = 0
+  private val returnMap = mutableMapOf<IrReturnableBlockSymbol, (IrReturn) -> IrExpression>()
+
+  override fun visitReturn(expression: IrReturn): IrExpression {
+    expression.transformChildrenVoid()
+    return returnMap[expression.returnTargetSymbol]?.invoke(expression) ?: expression
+  }
+
+  override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
+    if (expression !is IrReturnableBlock) return super.visitContainerExpression(expression)
+
+    val scopeSymbol = currentScope?.scope?.scopeOwnerSymbol ?: containerSymbol
+    val builder = DeclarationIrBuilder(context, scopeSymbol!!)
+    val variable by lazy {
+      builder.scope.createTmpVariable(expression.type, "tmp\$ret\$${labelCnt++}", true)
+    }
+
+    val loop by lazy {
+      IrDoWhileLoopImpl(
+        expression.startOffset,
+        expression.endOffset,
+        context.irBuiltIns.unitType,
+        expression.origin
+      ).apply {
+        label = "l\$ret\$${labelCnt++}"
+        condition = builder.irBoolean(false)
+      }
+    }
+
+    var hasReturned = false
+
+    returnMap[expression.symbol] = { returnExpression ->
+      hasReturned = true
+      builder.irComposite(returnExpression) {
+        +irSetVar(variable.symbol, returnExpression.value)
+        +irBreak(loop)
+      }
+    }
+
+    val newStatements = expression.statements.mapIndexed { i, s ->
+      if (i == expression.statements.lastIndex && s is IrReturn && s.returnTargetSymbol == expression.symbol) {
+        s.transformChildrenVoid()
+        if (!hasReturned) s.value else {
+          builder.irSetVar(variable.symbol, s.value)
+        }
+      } else {
+        s.transform(this, null)
+      }
+    }
+
+    returnMap.remove(expression.symbol)
+
+    if (!hasReturned) {
+      return IrCompositeImpl(
+        expression.startOffset,
+        expression.endOffset,
+        expression.type,
+        expression.origin,
+        newStatements
+      )
+    } else {
+      loop.body = IrBlockImpl(
+        expression.startOffset,
+        expression.endOffset,
+        context.irBuiltIns.unitType,
+        expression.origin,
+        newStatements
+      )
+
+      return builder.irComposite(expression, expression.origin) {
+        +variable
+        +loop
+        +irGet(variable)
+      }
+    }
+  }
+}
diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts
index 7d95a28..b366bbe 100644
--- a/sample/build.gradle.kts
+++ b/sample/build.gradle.kts
@@ -57,5 +57,5 @@ kotlin {
 }
 
 configure<com.bnorm.power.PowerAssertGradleExtension> {
-  functions = listOf("kotlin.test.assertTrue", "kotlin.require")
+  functions = listOf("kotlin.assert", "kotlin.test.assertTrue", "kotlin.require")
 }


Test patch:
diff --git a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/test.kt b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/test.kt
index e088ad4..86c8b0d 100644
--- a/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/test.kt
+++ b/kotlin-power-assert-plugin/src/test/kotlin/com/bnorm/power/test.kt
@@ -399,6 +399,20 @@ assert(a == 42)
        0
 """.trimIndent())
   }
+
+  @Test
+  fun constantExpression() {
+    assertMessage(
+      """
+fun main() {
+  assert(true)
+  assert(false)
+}""",
+      """
+Assertion failed
+""".trimIndent()
+    )
+  }
 }
 
 fun assertMessage(
diff --git a/sample/src/jsTest/kotlin/com/bnorm/power/JsPowerAssertTest.kt b/sample/src/jsTest/kotlin/com/bnorm/power/JsPowerAssertTest.kt
new file mode 100644
index 0000000..111d332
--- /dev/null
+++ b/sample/src/jsTest/kotlin/com/bnorm/power/JsPowerAssertTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 Brian Norman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bnorm.power
+
+import kotlin.test.Test
+
+class JsPowerAssertTest {
+  @Test
+  fun assert() {
+    require(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
+  }
+}
diff --git a/sample/src/jvmTest/kotlin/com/bnorm/power/JvmPowerAssertTest.kt b/sample/src/jvmTest/kotlin/com/bnorm/power/JvmPowerAssertTest.kt
index 233d3de..2a0f207 100644
--- a/sample/src/jvmTest/kotlin/com/bnorm/power/JvmPowerAssertTest.kt
+++ b/sample/src/jvmTest/kotlin/com/bnorm/power/JvmPowerAssertTest.kt
@@ -21,6 +21,6 @@ import kotlin.test.Test
 class JvmPowerAssertTest {
   @Test
   fun assert() {
-    assert(Person.UNKNOWN.size == 1)
+    assert(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
   }
 }
diff --git a/sample/src/nativeTest/kotlin/com/bnorm/power/NativePowerAssertTest.kt b/sample/src/nativeTest/kotlin/com/bnorm/power/NativePowerAssertTest.kt
new file mode 100644
index 0000000..267dcbb
--- /dev/null
+++ b/sample/src/nativeTest/kotlin/com/bnorm/power/NativePowerAssertTest.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 Brian Norman
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bnorm.power
+
+import kotlin.test.Test
+
+class NativePowerAssertTest {
+  @Test
+  fun assert() {
+    assert(Person.UNKNOWN.size == 1) { "unknown persons: ${Person.UNKNOWN}" }
+  }
+}