From e402b1bd56a4cf37f8d4ed7945c5aee697638622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 15 Aug 2024 19:52:34 +0200 Subject: [PATCH] feat(compat-filesystem): Add a way to read Java resources --- .../src/jvmMain/kotlin/resources/Resources.kt | 157 ++++++++++++++++++ .../src/jvmTest/kotlin/ResourcesTest.kt | 34 ++++ .../src/jvmTest/kotlin/pkg/PkgPackage.kt | 3 + .../prepared/compat/filesystem/pkg/test.txt | 1 + .../compat/filesystem/resource-text.txt | 1 + .../src/jvmTest/resources/root.txt | 1 + 6 files changed, 197 insertions(+) create mode 100644 compat/compat-filesystem/src/jvmMain/kotlin/resources/Resources.kt create mode 100644 compat/compat-filesystem/src/jvmTest/kotlin/ResourcesTest.kt create mode 100644 compat/compat-filesystem/src/jvmTest/kotlin/pkg/PkgPackage.kt create mode 100644 compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/pkg/test.txt create mode 100644 compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/resource-text.txt create mode 100644 compat/compat-filesystem/src/jvmTest/resources/root.txt diff --git a/compat/compat-filesystem/src/jvmMain/kotlin/resources/Resources.kt b/compat/compat-filesystem/src/jvmMain/kotlin/resources/Resources.kt new file mode 100644 index 0000000..64c1e67 --- /dev/null +++ b/compat/compat-filesystem/src/jvmMain/kotlin/resources/Resources.kt @@ -0,0 +1,157 @@ +package opensavvy.prepared.compat.filesystem.resources + +import opensavvy.prepared.suite.PreparedProvider +import opensavvy.prepared.suite.prepared +import java.io.InputStream +import java.net.URL + +internal interface ResourceLoader { + fun resourceAsStream(path: String): InputStream? + fun resourcePath(path: String): URL? +} + +/** + * Allow manipulating Java resources as part of a test. + * + * To obtain an instance of this class, see [resource]. + * + * Resources provide special helpers to: + * - [read] a resource. + */ +@ExperimentalResourceApi +class ResourceDesignator internal constructor( + private val loader: ResourceLoader, + private val path: String, +) { + + /** + * Allows reading a Java resource as a test fixture. + * + * ### Example + * + * ```kotlin + * private object CurrentFolder + * + * val test by resource("test.txt") + * .read() + * + * test("…") { + * check(test() == "Hello world!\n") + * } + * ``` + * + * To learn more about the way resources are loaded, see [resource]. + */ + @ExperimentalResourceApi + fun read(): PreparedProvider = + prepared { + val stream = loader.resourceAsStream(path) + ?: error("Could not load resource ${this@ResourceDesignator}. Ensure the file is declared as a resource in the correct package.") + + stream.bufferedReader().use { + it.readText() + } + } + + override fun toString(): String = + "${loader.resourcePath(path)} (from path '$path')" +} + +/** + * Loads a Java resource named [path] from [loader]. + * + * The resource will be accessed using [ClassLoader.getResource], [ClassLoader.getResourceAsStream], etc. + * That is to say, the resource should be stored in the root package of the [loader]. + * + * @see ResourceDesignator.read + */ +@ExperimentalResourceApi +fun resource(path: String, loader: ClassLoader) = ResourceDesignator( + loader = object : ResourceLoader { + override fun resourceAsStream(path: String): InputStream? = + loader.getResourceAsStream(path) + + override fun resourcePath(path: String): URL? = + loader.getResource(path) + }, + path = path, +) + +/** + * Loads a Java resource named [path] from [loader]. + * + * The resource will be accessed using [Class.getResource], [Class.getResourceAsStream], etc. + * That is to say, the resource should be stored in the same package as the given class. + * + * ### Example + * + * For this code to work: + * ```kotlin + * // File TestClass.kt + * package foo.bar.baz + * + * object TestClass + * + * val test by resource("test.txt", TestClass::class) + * .read() + * ``` + * the `test.txt` file should be placed in the exact same package as the `TestClass`. + * With typical Gradle configuration, the project may look like: + * ```text + * src/ + * main/ + * kotlin/ + * foo/ + * bar/ + * baz/ + * TestClass.kt + * resource/ + * foo/ + * bar/ + * baz/ + * test.txt + * ``` + * + * The overload that takes a single parameter is a shorthand for this function. + * + * @see ResourceDesignator.read + */ +@ExperimentalResourceApi +fun resource(path: String, loader: Class<*>) = ResourceDesignator( + loader = object : ResourceLoader { + override fun resourceAsStream(path: String): InputStream? = + loader.getResourceAsStream(path) + + override fun resourcePath(path: String): URL? = + loader.getResource(path) + }, + path = path, +) + +/** + * Loads a Java resource named [path] that is in the same package as [TargetClass]. + * + * ### Example + * + * ```kotlin + * package foo.bar.baz + * + * object TargetClass + * + * val test by resource("test.txt") + * .read() + * ``` + * For this example to work, the file `test.txt` should be in the same package as the `TargetClass`, meaning + * in the resources in subfolder `foo/bar/baz/test.txt`. + * + * To learn more about the location algorithm, see the overload that accepts a [Class]. + * + * @see ResourceDesignator.read + */ +@ExperimentalResourceApi +inline fun resource(path: String) = + resource(path, TargetClass::class.java) + +@MustBeDocumented +@RequiresOptIn("Experimental API to access and modify resources. Visit https://gitlab.com/opensavvy/groundwork/prepared/-/issues/66 to give your opinion before it is stabilized.") +annotation class ExperimentalResourceApi diff --git a/compat/compat-filesystem/src/jvmTest/kotlin/ResourcesTest.kt b/compat/compat-filesystem/src/jvmTest/kotlin/ResourcesTest.kt new file mode 100644 index 0000000..e4d5f0d --- /dev/null +++ b/compat/compat-filesystem/src/jvmTest/kotlin/ResourcesTest.kt @@ -0,0 +1,34 @@ +package opensavvy.prepared.compat.filesystem + +import opensavvy.prepared.compat.filesystem.pkg.PkgPackage +import opensavvy.prepared.compat.filesystem.resources.ExperimentalResourceApi +import opensavvy.prepared.compat.filesystem.resources.resource +import opensavvy.prepared.runner.kotest.PreparedSpec + +@OptIn(ExperimentalResourceApi::class) +class ResourcesTest : PreparedSpec({ + + suite("Read from different paths") { + val fromCurrentClass by resource("resource-text.txt") + .read() + + val fromSubPackage by resource("test.txt") + .read() + + val fromClassLoader by resource("root.txt", PkgPackage::class.java.classLoader) + .read() + + test("Load resource from current package") { + check(fromCurrentClass() == "From current package\n") + } + + test("Load resource from another package") { + check(fromSubPackage() == "From pkg package\n") + } + + test("Load resource from root class loader") { + check(fromClassLoader() == "From root\n") + } + } + +}) diff --git a/compat/compat-filesystem/src/jvmTest/kotlin/pkg/PkgPackage.kt b/compat/compat-filesystem/src/jvmTest/kotlin/pkg/PkgPackage.kt new file mode 100644 index 0000000..35e2a76 --- /dev/null +++ b/compat/compat-filesystem/src/jvmTest/kotlin/pkg/PkgPackage.kt @@ -0,0 +1,3 @@ +package opensavvy.prepared.compat.filesystem.pkg + +object PkgPackage diff --git a/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/pkg/test.txt b/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/pkg/test.txt new file mode 100644 index 0000000..fdf86d0 --- /dev/null +++ b/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/pkg/test.txt @@ -0,0 +1 @@ +From pkg package diff --git a/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/resource-text.txt b/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/resource-text.txt new file mode 100644 index 0000000..95b9978 --- /dev/null +++ b/compat/compat-filesystem/src/jvmTest/resources/opensavvy/prepared/compat/filesystem/resource-text.txt @@ -0,0 +1 @@ +From current package diff --git a/compat/compat-filesystem/src/jvmTest/resources/root.txt b/compat/compat-filesystem/src/jvmTest/resources/root.txt new file mode 100644 index 0000000..7bf9fbe --- /dev/null +++ b/compat/compat-filesystem/src/jvmTest/resources/root.txt @@ -0,0 +1 @@ +From root -- 2.51.2