From a655dec5833808aca5f16b05d4bd34cce77c2101 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Sun, 15 Dec 2024 22:33:31 +0100 Subject: [PATCH] Add unit testing --- README.md | 3 +- base/README.md | 2 + .../staropensource/engine/base/EngineTest.kt | 46 +++ .../engine/base/utility/FileAccessTest.kt | 268 ++++++++++++++++++ build.gradle.kts | 24 ++ dist/template-subproject/build.gradle.kts | 1 - gradle.properties | 7 + settings.gradle.kts | 1 + testapp/README.md | 5 +- testing/README.md | 4 + testing/build.gradle.kts | 35 +++ .../staropensource/engine/testing/TestBase.kt | 126 ++++++++ .../implementation/FailureShutdownHandler.kt | 51 ++++ .../NoOperationShutdownHandler.kt | 49 ++++ .../testing/implementation/package-info.kt | 27 ++ 15 files changed, 644 insertions(+), 5 deletions(-) create mode 100644 base/README.md create mode 100644 base/src/test/kotlin/de/staropensource/engine/base/EngineTest.kt create mode 100644 base/src/test/kotlin/de/staropensource/engine/base/utility/FileAccessTest.kt create mode 100644 testing/README.md create mode 100644 testing/build.gradle.kts create mode 100644 testing/src/main/kotlin/de/staropensource/engine/testing/TestBase.kt create mode 100644 testing/src/main/kotlin/de/staropensource/engine/testing/implementation/FailureShutdownHandler.kt create mode 100644 testing/src/main/kotlin/de/staropensource/engine/testing/implementation/NoOperationShutdownHandler.kt create mode 100644 testing/src/main/kotlin/de/staropensource/engine/testing/implementation/package-info.kt diff --git a/README.md b/README.md index f213237..388ca8e 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,7 @@ The engine is designed to be modular, configurable and extensible while being lightweight and fast. In this repository you can find the -[engine's logging system](https://git.staropensource.de/StarOpenSource/Engine/src/branch/develop/logging), -[engine core](https://git.staropensource.de/StarOpenSource/Engine/src/branch/develop/base), official +[core engine](https://git.staropensource.de/StarOpenSource/Engine/src/branch/develop/base), official subsystems, [their documentation](https://git.staropensource.de/StarOpenSource/Engine/src/branch/develop/docs) and [some miscellaneous files](https://git.staropensource.de/StarOpenSource/Engine/src/branch/develop/dist). diff --git a/base/README.md b/base/README.md new file mode 100644 index 0000000..31a7443 --- /dev/null +++ b/base/README.md @@ -0,0 +1,2 @@ +# `/base` +The core engine. The grand piece of code which makes everything work. diff --git a/base/src/test/kotlin/de/staropensource/engine/base/EngineTest.kt b/base/src/test/kotlin/de/staropensource/engine/base/EngineTest.kt new file mode 100644 index 0000000..f4df89f --- /dev/null +++ b/base/src/test/kotlin/de/staropensource/engine/base/EngineTest.kt @@ -0,0 +1,46 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base + +import de.staropensource.engine.base.exception.EngineInitializationFailureException +import de.staropensource.engine.base.implementable.ShutdownHandler +import de.staropensource.engine.testing.TestBase +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestMethodOrder + +@TestMethodOrder(MethodOrderer.MethodName::class) +class EngineTest : TestBase(autoManage = false) { + @Test + fun initialize() { + try { + Engine.initialize() + } catch (exception: EngineInitializationFailureException) { + fail("Engine failed to initialize", exception) + } + } + + @Test + fun shutdown() { + Engine.shutdown() + } +} diff --git a/base/src/test/kotlin/de/staropensource/engine/base/utility/FileAccessTest.kt b/base/src/test/kotlin/de/staropensource/engine/base/utility/FileAccessTest.kt new file mode 100644 index 0000000..cfd9d06 --- /dev/null +++ b/base/src/test/kotlin/de/staropensource/engine/base/utility/FileAccessTest.kt @@ -0,0 +1,268 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.utility + +import de.staropensource.engine.testing.TestBase +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import java.io.File +import kotlin.test.assertNotNull + +@Suppress("EmptyFunctionBlock") +class FileAccessTest : TestBase() { + // -----> Tests on non-existent files + // These tests are executed on paths which likely do not exist + @ParameterizedTest + @CsvSource( + delimiter = ',', + quoteCharacter = '"', + textBlock = """ + "/some/test/file", "/some/test/file" + "\very\nice\test\file.txt", "/very/nice/test/file.txt" + "/./did/somebody\\/say\\yoga?", "/did/somebody/say/yoga?" + "test.txt", "+%test.txt"""" + ) + fun toStringTest(supplyValue: String, compareValue: String) { + assertEquals( + compareValue + .replace("+", "${System.getProperty("user.dir")}") + .replace("%", File.separator), + FileAccess(supplyValue).toString() + ) + } + + @ParameterizedTest + @CsvSource( + delimiter = ',', + quoteCharacter = '"', + textBlock = """ + "/some/test/file", "%some%test%file" + "\very\nice\test\file.txt", "%very%nice%test%file.txt" + "/./did/somebody\////say\\\yoga?", "%did%somebody%say%yoga?" + "test.txt", "+%test.txt"""" + ) + fun toStringRaw(supplyValue: String, compareValue: String) { + assertEquals( + compareValue + .replace("+", "${System.getProperty("user.dir")}") + .replace("%", File.separator), + FileAccess(supplyValue).toStringRaw() + ) + } + + @ParameterizedTest + @CsvSource( + delimiter = ',', + quoteCharacter = '"', + textBlock = """ + "/some/test/file", "file" + "\very\nice\test\file.txt", "file.txt" + "/./did/somebody\\/say\\yoga?", "yoga?" + "test.txt", "test.txt"""" + ) + fun getBaseName(supplyValue: String, compareValue: String) { + assertEquals( + compareValue, + FileAccess(supplyValue).getBaseName() + ) + } + @ParameterizedTest + @CsvSource( + delimiter = ',', + quoteCharacter = '"', + textBlock = """ + "/some/test/file", "/some/test" + "\very\nice\test\file.txt", "/very/nice/test" + "/./did/somebody\\/say\\yoga?", "/did/somebody/say" + "test.txt", "+" + "configs/default.conf", "+%configs"""" + ) + fun parent(supplyValue: String, compareValue: String) { + assertEquals( + compareValue + .replace("+", "${System.getProperty("user.dir")}") + .replace("%", File.separator), + FileAccess(supplyValue).parent().toString() + ) + } + + + // -----> Tests on default paths + // These tests are executed on FileAccess' default paths + @Test + fun getTemporaryCacheDirectory() { + assertNotNull(FileAccess.temporaryCacheDirectory) + } + + @Test + fun getPersistentCacheDirectory() { + assertNotNull(FileAccess.persistentCacheDirectory) + } + + @Test + fun getHomeDirectory() { + assertNotNull(FileAccess.homeDirectory) + } + + @Test + fun getConfigDirectory() { + assertNotNull(FileAccess.configDirectory) + } + + @Test + fun getDataDirectory() { + assertNotNull(FileAccess.dataDirectory) + } + + @Test + fun exists() { + + } + + @Test + fun getType() { + + } + + @Test + fun isSymbolicLink() { + + } + + @Test + fun isHidden() { + + } + + @Test + fun isReadable() { + + } + + @Test + fun isWritable() { + + } + + @Test + fun isExecutable() { + + } + + @Test + fun getPosixPermissions() { + + } + + @Test + fun getLinkDestination() { + } + + @Test + fun getFileSystem() { + } + + @Test + fun isFilesystemPosixCompliant() { + } + + @Test + fun getFilesystemRestrictedNames() { + } + + @Test + fun createFile() { + } + + @Test + fun createDirectory() { + } + + @Test + fun createLink() { + } + + @Test + fun move() { + } + + @Test + fun copy() { + } + + @Test + fun delete() { + } + + @Test + fun deleteOnShutdown() { + } + + @Test + fun readBytes() { + } + + @Test + fun readLines() { + } + + @Test + fun readString() { + } + + @Test + fun writeBytes() { + } + + @Test + fun writeLines() { + } + + @Test + fun writeString() { + } + + @Test + fun appendBytes() { + } + + @Test + fun appendLines() { + } + + @Test + fun appendString() { + } + + @Test + fun list() { + } + + @Test + fun listFiles() { + } + + @Test + fun listDirectories() { + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 5259ab3..a76d143 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,6 +80,16 @@ allprojects { // OSHI implementation("${property("dependencyOshiIdentifier") as String}:${property("dependencyOshiVersion") as String}") + + // Unit testing + // -> Kotlin + testImplementation(kotlin("test")) + // -> JUnit + testImplementation(platform("${property("testDependencyJUnitBOMIdentifier")}:${property("testDependencyJUnitVersion")}")) + testImplementation("${property("testDependencyJUnitJupiterIdentifier")}") + testRuntimeOnly("${property("testDependencyJUnitPlatformLauncherIdentifier")}") + // -> sos!engine + testImplementation(project(":testing")) } // Java @@ -162,4 +172,18 @@ allprojects { } } } + + // Unit testing + // -> Configure Gradle to use JUnit + tasks.test { + useJUnitPlatform() + testLogging { + events( + "passed", + "skipped", + "failed" + ) + } + maxParallelForks = 1 + } } diff --git a/dist/template-subproject/build.gradle.kts b/dist/template-subproject/build.gradle.kts index bc133dc..6a8a6d9 100644 --- a/dist/template-subproject/build.gradle.kts +++ b/dist/template-subproject/build.gradle.kts @@ -24,6 +24,5 @@ dependencies { kotlin(property("dependencyKotlinStdIdentifier") as String) // sos!engine - implementation(project(":logging")) implementation(project(":base")) } diff --git a/gradle.properties b/gradle.properties index 2a3d851..746923e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,6 +24,7 @@ org.gradle.parallel=true org.gradle.priority=normal # Versioning +versionCodename=Sugarcane versionRelease=1 versionType=alpha versionTyperelease=10 @@ -44,3 +45,9 @@ dependencyOshiIdentifier=com.github.oshi:oshi-core-java11 dependencyOshiVersion=6.6.5 dependencyJansiIdentifier=org.fusesource:jansi dependencyJansiVersion=2.4.1 + +# Test dependencies +testDependencyJUnitVersion=5.11.3 +testDependencyJUnitBOMIdentifier=org.junit:junit-bom +testDependencyJUnitJupiterIdentifier=org.junit.jupiter:junit-jupiter +testDependencyJUnitPlatformLauncherIdentifier=org.junit.platform:junit-platform-launcher diff --git a/settings.gradle.kts b/settings.gradle.kts index 41ad465..819f6a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,3 +25,4 @@ rootProject.name = "sos!engine" include("") // scan root include("base") include("testapp") +include("testing") diff --git a/testapp/README.md b/testapp/README.md index 8792f68..4757e7e 100644 --- a/testapp/README.md +++ b/testapp/README.md @@ -1,2 +1,3 @@ -# `/dist/template-subproject` -This is a template subproject, simply existing to ease the process of creating a new Gradle subproject. +# `/testapp` +This is a development playground for the StarOpenSource Engine. +You can modify this test application to your liking while you are working on the engine. diff --git a/testing/README.md b/testing/README.md new file mode 100644 index 0000000..4a1f4bf --- /dev/null +++ b/testing/README.md @@ -0,0 +1,4 @@ +# `/testing` +This subproject handles all the heavy lifting when it comes to unit testing. +It heavily simplifies the process of creating unit tests involving the +StarOpenSource Engine. See the TestBase class for more information. diff --git a/testing/build.gradle.kts b/testing/build.gradle.kts new file mode 100644 index 0000000..71bbacd --- /dev/null +++ b/testing/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// Dependencies +dependencies { + // Kotlin support + kotlin(property("dependencyKotlinStdIdentifier") as String) + + // sos!engine + implementation(project(":base")) + + // Unit testing + // -> Kotlin + implementation(kotlin("test")) + // -> JUnit + implementation(platform("${property("testDependencyJUnitBOMIdentifier")}:${property("testDependencyJUnitVersion")}")) + implementation("${property("testDependencyJUnitJupiterIdentifier")}") +} diff --git a/testing/src/main/kotlin/de/staropensource/engine/testing/TestBase.kt b/testing/src/main/kotlin/de/staropensource/engine/testing/TestBase.kt new file mode 100644 index 0000000..7f9a1a4 --- /dev/null +++ b/testing/src/main/kotlin/de/staropensource/engine/testing/TestBase.kt @@ -0,0 +1,126 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.testing + +import de.staropensource.engine.base.Engine +import de.staropensource.engine.base.Engine.State +import de.staropensource.engine.base.EngineConfiguration +import de.staropensource.engine.base.type.logging.ChannelSettings +import de.staropensource.engine.testing.implementation.FailureShutdownHandler +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.fail + +/** + * Base class for implementing tests. + * + * @param autoManage automatically initializes and shuts the engine down after each test + * @param suppressInitAndShutdownLogging if to suppress the engine's log output during engine initialization and shutdown. Only takes effect if [autoManage] is `true` + * @param shutdownMarksFailure whether engine shutdowns should mark the test as failed + * @since v1-alpha10 + */ +abstract class TestBase( + val autoManage: Boolean = true, + val suppressInitAndShutdownLogging: Boolean = true, + val shutdownMarksFailure: Boolean = false +) { + init { + performConfiguration() + } + + /** + * Configures the engine before + * starting any tests. + * + * Absolutely mandatory or else tests + * may misbehave, which defeats the + * purpose of unit tests. + * + * @since v1-alpha10 + */ + private fun performConfiguration() { + if (shutdownMarksFailure) + EngineConfiguration.shutdownHandler = FailureShutdownHandler.instance + else + EngineConfiguration.shutdownHandler = FailureShutdownHandler.instance + + //if (Engine.state == State.UNINITIALIZED) {} + } + + /** + * Initializes the engine before each test. + * + * @since v1-alpha10 + */ + @BeforeEach + fun initializeEngine() { + when (Engine.state) { + State.INITIALIZING, State.SHUTTING_DOWN -> fail("Engine is in invalid state 'Engine.State.${Engine.state.name}'") + State.CRASHED -> fail("The StarOpenSource Engine has crashed") + else -> {} + } + + if (autoManage) { + val originalSettings: ChannelSettings = ChannelSettings.global + + // Set 'adapters' to an empty set + if (suppressInitAndShutdownLogging) + ChannelSettings.global = ChannelSettings.global.copy(adapters = linkedSetOf()) + + // Initialize the engine + Engine.initialize() + + // Restore channel configuration + if (suppressInitAndShutdownLogging) + ChannelSettings.global = originalSettings + } + } + + /** + * Shuts the engine down after each test + * + * @since v1-alpha10 + */ + @AfterEach + fun shutdownEngine() { + when (Engine.state) { + State.UNINITIALIZED -> fail("Internal inconsistency detected: Engine configuration was not performed") + State.INITIALIZING, State.SHUTTING_DOWN -> fail("Engine is in invalid state 'Engine.State.${Engine.state.name}'") + State.CRASHED -> fail("The StarOpenSource Engine has crashed") + else -> {} + } + + if (autoManage) { + val originalSettings: ChannelSettings = ChannelSettings.global + + // Set 'adapters' to an empty set + if (suppressInitAndShutdownLogging) + ChannelSettings.global = ChannelSettings.global.copy(adapters = linkedSetOf()) + + // Shut the engine down + Engine.shutdown() + + // Restore channel configuration + if (suppressInitAndShutdownLogging) + ChannelSettings.global = originalSettings + } + } +} diff --git a/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/FailureShutdownHandler.kt b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/FailureShutdownHandler.kt new file mode 100644 index 0000000..70c5c59 --- /dev/null +++ b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/FailureShutdownHandler.kt @@ -0,0 +1,51 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.testing.implementation + +import de.staropensource.engine.base.implementable.ShutdownHandler +import kotlin.test.fail + +/** + * [ShutdownHandler] implementation which + * causes the test to fail if executed. + * + * @since v1-alpha10 + */ +class FailureShutdownHandler private constructor() : ShutdownHandler { + /** + * Companion object of [FailureShutdownHandler]. + * + * @since v1-alpha10 + */ + companion object { + /** + * Global instance of [FailureShutdownHandler]. + * + * @since v1-alpha10 + */ + @JvmStatic + val instance: FailureShutdownHandler = FailureShutdownHandler() + } + + override fun exit(exitcode: UByte) { + fail("The engine was shut down with code ${exitcode}") + } +} diff --git a/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/NoOperationShutdownHandler.kt b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/NoOperationShutdownHandler.kt new file mode 100644 index 0000000..f53455e --- /dev/null +++ b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/NoOperationShutdownHandler.kt @@ -0,0 +1,49 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.testing.implementation + +import de.staropensource.engine.base.implementable.ShutdownHandler +import kotlin.test.fail + +/** + * [ShutdownHandler] implementation which + * causes the test to fail if executed. + * + * @since v1-alpha10 + */ +class NoOperationShutdownHandler private constructor() : ShutdownHandler { + /** + * Companion object of [NoOperationShutdownHandler]. + * + * @since v1-alpha10 + */ + companion object { + /** + * Global instance of [NoOperationShutdownHandler]. + * + * @since v1-alpha10 + */ + @JvmStatic + val instance: NoOperationShutdownHandler = NoOperationShutdownHandler() + } + + override fun exit(exitcode: UByte) = Unit +} diff --git a/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/package-info.kt b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/package-info.kt new file mode 100644 index 0000000..ef6d54d --- /dev/null +++ b/testing/src/main/kotlin/de/staropensource/engine/testing/implementation/package-info.kt @@ -0,0 +1,27 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Implementations of various + * interfaces and abstract classes. + * + * @since v1-alpha10 + */ +package de.staropensource.engine.testing.implementation