diff --git a/.github/scripts/update-readme-version.sh b/.github/scripts/update-readme-version.sh index 864e525..83b4a77 100755 --- a/.github/scripts/update-readme-version.sh +++ b/.github/scripts/update-readme-version.sh @@ -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 @@ -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" \ No newline at end of file +echo "Successfully updated version to $VERSION in README.md" diff --git a/README.md b/README.md index fcd788d..d16a6fa 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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().configureEach { + environment("GRADLE_ROOT_DIR", gradleRootDir) + environment("FOO", fooValue) +} + +tasks.withType().configureEach { + environment("GRADLE_ROOT_DIR", gradleRootDir) + environment("FOO", fooValue) +} + +tasks.withType().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: diff --git a/api/xemantic-kotlin-test.api b/api/xemantic-kotlin-test.api index abb0f44..f499792 100644 --- a/api/xemantic-kotlin-test.api +++ b/api/xemantic-kotlin-test.api @@ -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 (Ljava/lang/String;Ljava/lang/AssertionError;)V } +public final class com/xemantic/kotlin/test/TestContextKt { + public static final fun getGradleRootDir ()Ljava/lang/String; +} + diff --git a/build.gradle.kts b/build.gradle.kts index 65c7b01..0936cf7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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) @@ -45,6 +49,26 @@ repositories { mavenCentral() } +val gradleRootDir: String = rootDir.absolutePath +val fooValue = "bar" + +tasks.withType().configureEach { + environment("GRADLE_ROOT_DIR", gradleRootDir) + environment("FOO", fooValue) +} + +tasks.withType().configureEach { + environment("GRADLE_ROOT_DIR", gradleRootDir) + environment("FOO", fooValue) +} + +tasks.withType().configureEach { + environment("GRADLE_ROOT_DIR", gradleRootDir) + environment("SIMCTL_CHILD_GRADLE_ROOT_DIR", gradleRootDir) + environment("FOO", fooValue) + environment("SIMCTL_CHILD_FOO", fooValue) +} + kotlin { applyDefaultHierarchyTemplate() @@ -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 { testLogging { diff --git a/src/commonMain/kotlin/TestContext.kt b/src/commonMain/kotlin/TestContext.kt new file mode 100644 index 0000000..b8a13d8 --- /dev/null +++ b/src/commonMain/kotlin/TestContext.kt @@ -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")!! diff --git a/src/commonTest/kotlin/TestContextTest.kt b/src/commonTest/kotlin/TestContextTest.kt new file mode 100644 index 0000000..bf229ea --- /dev/null +++ b/src/commonTest/kotlin/TestContextTest.kt @@ -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) + } + +} diff --git a/src/jsMain/kotlin/JsTestContext.kt b/src/jsMain/kotlin/JsTestContext.kt new file mode 100644 index 0000000..b83cad0 --- /dev/null +++ b/src/jsMain/kotlin/JsTestContext.kt @@ -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 diff --git a/src/jvmMain/kotlin/JvmTestContext.kt b/src/jvmMain/kotlin/JvmTestContext.kt new file mode 100644 index 0000000..7bfacbe --- /dev/null +++ b/src/jvmMain/kotlin/JvmTestContext.kt @@ -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 diff --git a/src/nativeMain/kotlin/NativeTestContext.kt b/src/nativeMain/kotlin/NativeTestContext.kt new file mode 100644 index 0000000..bea3703 --- /dev/null +++ b/src/nativeMain/kotlin/NativeTestContext.kt @@ -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 diff --git a/src/wasmJsMain/kotlin/WasmJsTestContext.kt b/src/wasmJsMain/kotlin/WasmJsTestContext.kt new file mode 100644 index 0000000..850d943 --- /dev/null +++ b/src/wasmJsMain/kotlin/WasmJsTestContext.kt @@ -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'" +) diff --git a/src/wasmWasiMain/kotlin/WasmWasiTestContext.kt b/src/wasmWasiMain/kotlin/WasmWasiTestContext.kt new file mode 100644 index 0000000..3ff2f50 --- /dev/null +++ b/src/wasmWasiMain/kotlin/WasmWasiTestContext.kt @@ -0,0 +1,24 @@ +/* + * 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 + +// it seems doable, however requires lots of memory management and native magic +public actual fun getEnv( + name: String +): String? = TODO() + +public actual val isBrowserPlatform: Boolean = TODO() diff --git a/webpack.config.d/env-config.js b/webpack.config.d/env-config.js new file mode 100644 index 0000000..36d56b2 --- /dev/null +++ b/webpack.config.d/env-config.js @@ -0,0 +1,9 @@ +const webpack = require("webpack"); +const envPlugin = new webpack.DefinePlugin({ + 'process': { + 'env': { + 'FOO': JSON.stringify(process.env.FOO) + } + } +}); +config.plugins.push(envPlugin);