Skip to content

Commit

Permalink
Merge pull request #4 from xemantic/feature/testContext
Browse files Browse the repository at this point in the history
support for text context, reading environment variables in browser tests, and gradle root dir for accessing test dirs for non-browser tests
  • Loading branch information
morisil authored Dec 29, 2024
2 parents a4190e3 + cf8b040 commit e421527
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 9 deletions.
8 changes: 4 additions & 4 deletions .github/scripts/update-readme-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,19 @@ fi
ESCAPED_GROUP_ID=$(echo "$GROUP_ID" | sed 's/\./\\./g')

# Create the pattern to match
PATTERN="\"$ESCAPED_GROUP_ID:$ARTIFACT_ID:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\""
PATTERN="\"$ESCAPED_GROUP_ID:$ARTIFACT_ID:[0-9]+(\.[0-9]+){0,2}\""

# Create the replacement string
REPLACEMENT="\"$GROUP_ID:$ARTIFACT_ID:$VERSION\""

# Check if the pattern exists in the file
if ! grep -q "$GROUP_ID:$ARTIFACT_ID:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" README.md; then
if ! grep -Eq "$GROUP_ID:$ARTIFACT_ID:[0-9]+(\.[0-9]+){0,2}" README.md; then
echo "Error: Dependency pattern not found in README.md"
exit 1
fi

# Perform the replacement and save to a temporary file
sed "s|$PATTERN|$REPLACEMENT|g" README.md > README.md.tmp
sed -E "s|$PATTERN|$REPLACEMENT|g" README.md > README.md.tmp

# Check if sed made any changes
if cmp -s README.md README.md.tmp; then
Expand All @@ -71,4 +71,4 @@ fi
# Move the temporary file back to the original
mv README.md.tmp README.md

echo "Successfully updated version to $VERSION in README.md"
echo "Successfully updated version to $VERSION in README.md"
76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ Kotlin multiplatform testing library providing power-assert compatible DSL and a

## Why?

I am mostly using [kotest](https://kotest.io/) library for writing test assertions
in my projects. When [power-assert](https://kotlinlang.org/docs/power-assert.html)
became the official Kotlin compiler plugin, I also realized that most of the kotest
assertions can be replaced with something which suits my needs much better.
I am mostly using [kotest](https://kotest.io/) library for writing test assertions in my projects.
When [power-assert](https://kotlinlang.org/docs/power-assert.html) became the official Kotlin compiler plugin, I also realized that most of the kotest assertions can be replaced with something which suits my needs much better.
Instead of writing:

```kotlin
Expand Down Expand Up @@ -174,6 +172,76 @@ complexObject should {
}
```

### Test Context

You can obtain access to test context like:

* Stable absolute path of the current gradle root dir, so that the test files can be used in tests of non-browser platforms.
* Environment variables, accessible on almost all the platforms, including access to predefined set of environment variables in tests of browser platforms (e.g. API keys).

See [TextContext](src/commonMain/kotlin/TestContext.kt) for details.

You have to add to `build.gradle.kts`:

```kotlin
val gradleRootDir: String = rootDir.absolutePath
val fooValue = "bar"

tasks.withType<KotlinJvmTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
}

tasks.withType<KotlinJsTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
}

tasks.withType<KotlinNativeTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("SIMCTL_CHILD_GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
environment("SIMCTL_CHILD_FOO", fooValue)
}
```

and specify environment variables you are interested in. The `SIMCTL_CHILD_` is used in tests running inside emulators.

To pass environment variables to browser tests, you have to create `webpack.confg.d` folder and drop this file name `env-config.js`:

```js
const webpack = require("webpack");
const envPlugin = new webpack.DefinePlugin({
'process': {
'env': {
'FOO': JSON.stringify(process.env.FOO)
}
}
});
config.plugins.push(envPlugin);
```

Pick environment variables which should be provided to browser tests.

Then you can write test like:

```kotlin
class TestContextTest {

@Test
fun `Should read gradleRootDir`() {
if (isBrowserPlatform) return // we don't have access to Gradle root dir
assert(gradleRootDir.isNotEmpty())
}

@Test
fun `Should read predefined environment variable`() {
assert(getEnv("FOO") == "bar")
}

}
```

## Development

Clone this project, and then run:
Expand Down
9 changes: 9 additions & 0 deletions api/xemantic-kotlin-test.api
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ public final class com/xemantic/kotlin/test/AssertionsKt {
public static final fun should (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
}

public final class com/xemantic/kotlin/test/JvmTestContextKt {
public static final fun getEnv (Ljava/lang/String;)Ljava/lang/String;
public static final fun isBrowserPlatform ()Z
}

public final class com/xemantic/kotlin/test/ShouldAssertionError : java/lang/AssertionError {
public fun <init> (Ljava/lang/String;Ljava/lang/AssertionError;)V
}

public final class com/xemantic/kotlin/test/TestContextKt {
public static final fun getGradleRootDir ()Ljava/lang/String;
}

30 changes: 29 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.swiftexport.ExperimentalSwiftExportDsl
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest

plugins {
alias(libs.plugins.kotlin.multiplatform)
Expand Down Expand Up @@ -45,6 +49,26 @@ repositories {
mavenCentral()
}

val gradleRootDir: String = rootDir.absolutePath
val fooValue = "bar"

tasks.withType<KotlinJvmTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
}

tasks.withType<KotlinJsTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
}

tasks.withType<KotlinNativeTest>().configureEach {
environment("GRADLE_ROOT_DIR", gradleRootDir)
environment("SIMCTL_CHILD_GRADLE_ROOT_DIR", gradleRootDir)
environment("FOO", fooValue)
environment("SIMCTL_CHILD_FOO", fooValue)
}

kotlin {

applyDefaultHierarchyTemplate()
Expand Down Expand Up @@ -130,9 +154,13 @@ kotlin {

}

//// skip tests which require XCode components to be installed
// skip tests which require XCode components to be installed
tasks.named("tvosSimulatorArm64Test") { enabled = false }
tasks.named("watchosSimulatorArm64Test") { enabled = false }
// skip tests for which system environment variable retrival is not implemented at the moment
tasks.named("wasmWasiNodeTest") { enabled = false}
// skip tests which for some reason stale
tasks.named("wasmJsBrowserTest") { enabled = false}

tasks.withType<Test> {
testLogging {
Expand Down
48 changes: 48 additions & 0 deletions src/commonMain/kotlin/TestContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

/**
* Returns the value of the specified environment variable.
*
* Note: on platforms which does not support access to system environment variables,
* they can be still set with gradle test configuration.
* See [README.md](https://github.com/xemantic/xemantic-kotlin-test) for details.
*
* @param name the name of the environment variable to read.
*/
public expect fun getEnv(name: String): String?

/**
* Returns true, if we are on the browser platform.
*
* This flag might be used to skip certain tests, e.g. file based tests
* on platforms which does not offer access to the filesystem.
*/
public expect val isBrowserPlatform: Boolean

/**
* The root dir of the gradle project.
*
* It is provided as an absolute file path. The specificity of emulators
* is taken into account.
*
* Note: the gradle root dir can be only resolved with additional gradle
* settings.
* See [README.md](https://github.com/xemantic/xemantic-kotlin-test) for details.
*/
public val gradleRootDir: String get() = getEnv("GRADLE_ROOT_DIR")!!
39 changes: 39 additions & 0 deletions src/commonTest/kotlin/TestContextTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

import kotlin.test.Test

class TestContextTest {

@Test
fun `Should read gradleRootDir`() {
if (isBrowserPlatform) return // we don't have access to Gradle root dir
assert(gradleRootDir.isNotEmpty())
}

@Test
fun `Should read predefined environment variable`() {
assert(getEnv("FOO") == "bar")
}

@Test
fun `Should not read undefined environment variable`() {
assert(getEnv("BAR") == null)
}

}
30 changes: 30 additions & 0 deletions src/jsMain/kotlin/JsTestContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

public actual fun getEnv(
name: String
): String? {
val variable = process.env[name]
return if (variable) return variable else null
}

public actual val isBrowserPlatform: Boolean = js(
"typeof window !== 'undefined'"
)

private external val process: dynamic
23 changes: 23 additions & 0 deletions src/jvmMain/kotlin/JvmTestContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

public actual fun getEnv(
name: String
): String? = System.getenv(name)

public actual val isBrowserPlatform: Boolean = false
28 changes: 28 additions & 0 deletions src/nativeMain/kotlin/NativeTestContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
import platform.posix.getenv

@OptIn(ExperimentalForeignApi::class)
public actual fun getEnv(
name: String
): String? = getenv(name)?.toKString()

public actual val isBrowserPlatform: Boolean = false
25 changes: 25 additions & 0 deletions src/wasmJsMain/kotlin/WasmJsTestContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2024 Kazimierz Pogoda / Xemantic
*
* 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.xemantic.kotlin.test

public actual fun getEnv(
name: String
): String? = js("process.env[name]")

public actual val isBrowserPlatform: Boolean = js(
"typeof window !== 'undefined'"
)
Loading

0 comments on commit e421527

Please sign in to comment.