diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4803880..0ada101 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.re gradle-publish = { module = "com.gradle.publish:plugin-publish-plugin", version = "1.2.0" } jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "jacoco" } +plexus-interpolation = { module = "org.codehaus.plexus:plexus-interpolation", version = "1.29" } # test junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } diff --git a/junit5/build.gradle.kts b/junit5/build.gradle.kts index ea16c8c..92ca28d 100644 --- a/junit5/build.gradle.kts +++ b/junit5/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(libs.junit) implementation(gradleTestKit()) implementation(libs.jacoco.core) + implementation(libs.plexus.interpolation) testImplementation(libs.strikt.core) } diff --git a/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TestProjectExtension.kt b/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TestProjectExtension.kt index bf21de3..2e51a38 100644 --- a/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TestProjectExtension.kt +++ b/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TestProjectExtension.kt @@ -28,10 +28,8 @@ import org.junit.jupiter.params.provider.ArgumentsProvider import java.nio.file.Path import java.util.concurrent.ConcurrentHashMap import java.util.stream.Stream -import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.absolutePathString import kotlin.io.path.appendText -import kotlin.io.path.copyToRecursively import kotlin.io.path.createFile import kotlin.io.path.createTempDirectory import kotlin.io.path.createTempFile @@ -138,7 +136,6 @@ class TestProjectExtension : noinline f: (K) -> V ) = getOrComputeIfAbsent(key, f, V::class.java) - @OptIn(ExperimentalPathApi::class) private fun ExtensionContext.project(gradleVersion: GradleVersionArgument): TestProject { val parameters = requiredTestClass.getAnnotation(TestKit::class.java) ?: TestKit() @@ -161,7 +158,7 @@ class TestProjectExtension : error { "expected a test project in $location" } } - location.copyToRecursively(target = tempProjectDir, followLinks = false, overwrite = false) + TokenReplacer.copyAndReplace(location, tempProjectDir, gradleVersion.properties) createProject( tempProjectDir, diff --git a/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TokenReplacer.kt b/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TokenReplacer.kt new file mode 100644 index 0000000..5350d89 --- /dev/null +++ b/junit5/src/main/kotlin/com/toasttab/gradle/testkit/TokenReplacer.kt @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2026 Toast Inc. + * + * 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.toasttab.gradle.testkit + +import org.codehaus.plexus.interpolation.MapBasedValueSource +import org.codehaus.plexus.interpolation.StringSearchInterpolator +import org.codehaus.plexus.interpolation.multi.MultiDelimiterInterpolatorFilterReader +import java.nio.charset.StandardCharsets +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.PathMatcher +import java.util.Properties +import kotlin.io.path.CopyActionResult +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.copyTo +import kotlin.io.path.copyToRecursively +import kotlin.io.path.isRegularFile + +object TokenReplacer { + private val fileTokens: Map by lazy { + val path = System.getProperty("testkit-tokens") ?: return@lazy emptyMap() + val file = Path.of(path) + if (!Files.exists(file)) return@lazy emptyMap() + + val props = Properties() + file.toFile().inputStream().use { props.load(it) } + props.stringPropertyNames().associateWith { props.getProperty(it) } + } + + // Globs restricting which files are interpolated. Empty means every file is filtered; + // otherwise files that match no glob are copied verbatim. Each glob is matched against both + // the path relative to the test project root and the bare file name, so "*.gradle.kts" filters + // build scripts at any depth. Usually narrowed to the build scripts, e.g. + // "*.gradle.kts,*.gradle". + private val filterMatchers: List by lazy { + parseMatchers(System.getProperty("testkit-filter-includes")) + } + + internal fun parseMatchers(spec: String?): List { + if (spec == null) return emptyList() + return spec + .split(',', '\n') + .map { it.trim() } + .filter { it.isNotEmpty() } + .map { FileSystems.getDefault().getPathMatcher("glob:$it") } + } + + internal fun shouldFilter( + relative: Path, + matchers: List + ) = matchers.isEmpty() || matchers.any { it.matches(relative) || it.matches(relative.fileName) } + + fun copyAndReplace( + source: Path, + target: Path, + extraTokens: Map + ) { + val tokens = if (extraTokens.isEmpty()) fileTokens else fileTokens + extraTokens + copyAndReplace(source, target, tokens, filterMatchers) + } + + @OptIn(ExperimentalPathApi::class) + internal fun copyAndReplace( + source: Path, + target: Path, + tokens: Map, + matchers: List + ) { + if (tokens.isEmpty()) { + source.copyToRecursively(target = target, followLinks = false, overwrite = false) + return + } + + val valueSource = MapBasedValueSource(tokens) + + source.copyToRecursively(target = target, followLinks = false) { src, tgt -> + when { + !src.isRegularFile() -> Files.createDirectories(tgt) + shouldFilter(source.relativize(src), matchers) -> filterCopy(src, tgt, valueSource) + else -> src.copyTo(tgt, overwrite = false) + } + CopyActionResult.CONTINUE + } + } + + private fun filterCopy( + source: Path, + target: Path, + valueSource: MapBasedValueSource + ) { + // ISO-8859-1 round-trips every byte as a distinct char, so binary files pass through + // untouched and UTF-8 text files are preserved byte-for-byte. Only ASCII @KEY@ tokens + // need to match, and those map identically in both encodings. + Files.newBufferedReader(source, StandardCharsets.ISO_8859_1).use { reader -> + val interpolator = StringSearchInterpolator("@", "@") + interpolator.addValueSource(valueSource) + + val filtered = + MultiDelimiterInterpolatorFilterReader(reader, interpolator).apply { + addDelimiterSpec("@*@") + } + + Files.newBufferedWriter(target, StandardCharsets.ISO_8859_1).use { writer -> + filtered.copyTo(writer) + } + } + } +} diff --git a/junit5/src/test/kotlin/com/toasttab/gradle/testkit/GradleVersionPropertiesIntegrationTest.kt b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/GradleVersionPropertiesIntegrationTest.kt index b69b218..6103730 100644 --- a/junit5/src/test/kotlin/com/toasttab/gradle/testkit/GradleVersionPropertiesIntegrationTest.kt +++ b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/GradleVersionPropertiesIntegrationTest.kt @@ -16,6 +16,7 @@ package com.toasttab.gradle.testkit import strikt.api.expectThat +import strikt.assertions.contains import strikt.assertions.isEqualTo @TestKit( @@ -42,4 +43,22 @@ class GradleVersionPropertiesIntegrationTest { expectThat(project.property("kotlin")).isEqualTo(expectedKotlin) } + + @ParameterizedWithGradleVersions + fun `per-version properties are used as replacement tokens`(project: TestProject) { + val expectedKotlin = + when (project.gradleVersion.version) { + "8.6" -> "1.9.24" + "8.7" -> "2.0.0" + else -> error("unexpected gradle version ${project.gradleVersion.version}") + } + + val buildFile = + project.dir + .resolve("build.gradle.kts") + .toFile() + .readText() + + expectThat(buildFile).contains("// kotlin token: $expectedKotlin") + } } diff --git a/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerIntegrationTest.kt b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerIntegrationTest.kt new file mode 100644 index 0000000..a172397 --- /dev/null +++ b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerIntegrationTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2026 Toast Inc. + * + * 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.toasttab.gradle.testkit + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import strikt.api.expectThat +import strikt.assertions.containsExactly +import strikt.assertions.isEqualTo +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.readBytes +import kotlin.io.path.readText +import kotlin.io.path.writeBytes +import kotlin.io.path.writeText + +class TokenReplacerIntegrationTest { + @TempDir + lateinit var source: Path + + @TempDir + lateinit var target: Path + + // 0x00..0xFF, including bytes that are not valid UTF-8, so we prove binary files round-trip. + private val binary = ByteArray(256) { it.toByte() } + + @Test + fun `interpolates matching files while copying others verbatim`() { + source.resolve("build.gradle.kts").writeText("kotlin = \"@kotlin@\"") + source.resolve("nested").createDirectories() + source.resolve("nested/settings.gradle.kts").writeText("name = \"@name@\"") + source.resolve("gradle.properties").writeText("kept = @kotlin@") + source.resolve("logo.png").writeBytes(binary) + + TokenReplacer.copyAndReplace( + source, + target, + tokens = mapOf("kotlin" to "2.3.21", "name" to "demo"), + matchers = TokenReplacer.parseMatchers("*.gradle.kts") + ) + + // build scripts at any depth have their tokens replaced + expectThat(target.resolve("build.gradle.kts").readText()).isEqualTo("kotlin = \"2.3.21\"") + expectThat(target.resolve("nested/settings.gradle.kts").readText()).isEqualTo("name = \"demo\"") + + // files that match no glob are copied verbatim, tokens untouched + expectThat(target.resolve("gradle.properties").readText()).isEqualTo("kept = @kotlin@") + + // binary files round-trip byte-for-byte + expectThat(target.resolve("logo.png").readBytes().toList()).containsExactly(binary.toList()) + } + + @Test + fun `no matchers filters every file`() { + source.resolve("build.gradle.kts").writeText("kotlin = \"@kotlin@\"") + source.resolve("gradle.properties").writeText("kept = @kotlin@") + + TokenReplacer.copyAndReplace( + source, + target, + tokens = mapOf("kotlin" to "2.3.21"), + matchers = emptyList() + ) + + expectThat(target.resolve("build.gradle.kts").readText()).isEqualTo("kotlin = \"2.3.21\"") + expectThat(target.resolve("gradle.properties").readText()).isEqualTo("kept = 2.3.21") + } +} diff --git a/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerTest.kt b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerTest.kt new file mode 100644 index 0000000..29555db --- /dev/null +++ b/junit5/src/test/kotlin/com/toasttab/gradle/testkit/TokenReplacerTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026 Toast Inc. + * + * 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.toasttab.gradle.testkit + +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.isEmpty +import strikt.assertions.isFalse +import strikt.assertions.isTrue +import java.nio.file.Path + +class TokenReplacerTest { + @Test + fun `no includes configured filters every file`() { + val matchers = TokenReplacer.parseMatchers(null) + + expectThat(matchers).isEmpty() + expectThat(TokenReplacer.shouldFilter(Path.of("anything.bin"), matchers)).isTrue() + expectThat(TokenReplacer.shouldFilter(Path.of("a/b/c.gradle.kts"), matchers)).isTrue() + } + + @Test + fun `blank includes filters every file`() { + val matchers = TokenReplacer.parseMatchers(" , \n ") + + expectThat(matchers).isEmpty() + expectThat(TokenReplacer.shouldFilter(Path.of("anything.bin"), matchers)).isTrue() + } + + @Test + fun `bare name globs match files at any depth`() { + val matchers = TokenReplacer.parseMatchers("*.gradle.kts, *.gradle") + + expectThat(TokenReplacer.shouldFilter(Path.of("build.gradle.kts"), matchers)).isTrue() + expectThat(TokenReplacer.shouldFilter(Path.of("sub/build.gradle.kts"), matchers)).isTrue() + expectThat(TokenReplacer.shouldFilter(Path.of("settings.gradle"), matchers)).isTrue() + expectThat(TokenReplacer.shouldFilter(Path.of("gradle.properties"), matchers)).isFalse() + expectThat(TokenReplacer.shouldFilter(Path.of("src/main/Foo.kt"), matchers)).isFalse() + } + + @Test + fun `path globs match against the relative path`() { + val matchers = TokenReplacer.parseMatchers("**/*.gradle.kts") + + expectThat(TokenReplacer.shouldFilter(Path.of("sub/build.gradle.kts"), matchers)).isTrue() + expectThat(TokenReplacer.shouldFilter(Path.of("src/main/Foo.kt"), matchers)).isFalse() + } +} diff --git a/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/build.gradle.kts b/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/build.gradle.kts new file mode 100644 index 0000000..821501b --- /dev/null +++ b/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { + java +} + +// kotlin token: @kotlin@ diff --git a/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/settings.gradle.kts b/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/settings.gradle.kts new file mode 100644 index 0000000..633b00b --- /dev/null +++ b/junit5/src/test/projects/GradleVersionPropertiesIntegrationTest/per-version properties are used as replacement tokens/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "per-version-tokens" diff --git a/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitExtension.kt b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitExtension.kt index 3f21da2..4ff8e13 100644 --- a/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitExtension.kt +++ b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitExtension.kt @@ -15,6 +15,7 @@ package com.toasttab.gradle.testkit +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -22,10 +23,20 @@ abstract class TestkitExtension { abstract val testProjectsDir: Property abstract val replaceTokens: MapProperty + // Globs restricting which files have their tokens replaced. When empty, every file is filtered. + // Each glob is matched against both the path relative to the test project root and the bare + // file name, so filterIncludes("*.gradle.kts", "*.gradle") narrows filtering to the build + // scripts at any depth. + abstract val filterIncludes: ListProperty + fun replaceToken( name: String, value: String ) { replaceTokens.put(name, value) } + + fun filterIncludes(vararg globs: String) { + filterIncludes.addAll(*globs) + } } diff --git a/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitPlugin.kt b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitPlugin.kt index 8c80630..fd66967 100644 --- a/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitPlugin.kt +++ b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/TestkitPlugin.kt @@ -17,16 +17,13 @@ package com.toasttab.gradle.testkit import com.toasttab.gradle.testkit.shared.configureIntegrationPublishing import com.toasttab.gradle.testkit.shared.integrationDirectory -import org.apache.tools.ant.filters.ReplaceTokens import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.type.ArtifactTypeDefinition import org.gradle.api.file.FileSystemOperations -import org.gradle.api.tasks.Copy import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.create -import org.gradle.kotlin.dsl.filter import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register import org.gradle.testing.jacoco.tasks.JacocoReportBase @@ -40,43 +37,49 @@ class TestkitPlugin override fun apply(project: Project) { val extension = project.extensions.create("testkitTests") val destfile = project.layout.buildDirectory.file("jacoco/testkit.exec") - val testProjectDir = project.layout.buildDirectory.dir("test-projects") + val tokensFile = project.layout.buildDirectory.file("testkit/tokens.properties") + val testProjectsDir = + extension.testProjectsDir + .map { project.layout.projectDirectory.dir(it) } + .orElse(project.layout.projectDirectory.dir("src/test/projects")) - project.tasks.register("copyTestProjects") { - from(extension.testProjectsDir.getOrElse("src/test/projects")) - into(testProjectDir) - - val tokens = - mapOf( - "TESTKIT_PLUGIN_VERSION" to BuildConfig.VERSION, - "TESTKIT_INTEGRATION_REPO" to project.integrationDirectory().path, - "VERSION" to "${project.version}" - ) + extension.replaceTokens.get() - - // default up-to-date checks ignore the tokens - inputs.property("tokens", tokens) + val writeTokens = + project.tasks.register("writeTestkitTokens") { + tokens.putAll(extension.replaceTokens) + tokens.put("TESTKIT_PLUGIN_VERSION", BuildConfig.VERSION) + tokens.put("TESTKIT_INTEGRATION_REPO", project.integrationDirectory().path) + tokens.put("VERSION", project.provider { "${project.version}" }) + outputFile.set(tokensFile) + } - filter( - mapOf("tokens" to tokens) - ) - } + val filterIncludes = extension.filterIncludes.map { it.joinToString(",") }.orElse("") project.tasks.named("test") { - dependsOn("copyTestProjects") + dependsOn(writeTokens) doFirst(JacocoOutputCleanupTestTaskAction(fs, destfile)) inputs - .dir(testProjectDir) + .property("testkit-filter-includes", filterIncludes) + + inputs + .dir(testProjectsDir) .withPropertyName("testkit-projects-input") .withPathSensitivity(PathSensitivity.RELATIVE) + inputs + .file(tokensFile) + .withPropertyName("testkit-tokens-input") + .withPathSensitivity(PathSensitivity.NONE) + // declare an additional jacoco output file so that the JUnit JVM and the TestKit JVM // do not try to write to the same file outputs.file(destfile).withPropertyName("testkit-coverage-output") systemProperty("testkit-coverage-output", "${destfile.get()}") - systemProperty("testkit-projects", "${testProjectDir.get()}") + systemProperty("testkit-projects", testProjectsDir.get().asFile.path) + systemProperty("testkit-tokens", tokensFile.get().asFile.path) + systemProperty("testkit-filter-includes", filterIncludes.get()) systemProperty("testkit-integration-repo", project.integrationDirectory().path) systemProperty("testkit-plugin-version", BuildConfig.VERSION) } diff --git a/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/WriteTokensPropertiesTask.kt b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/WriteTokensPropertiesTask.kt new file mode 100644 index 0000000..e9106df --- /dev/null +++ b/testkit-plugin/src/main/kotlin/com/toasttab/gradle/testkit/WriteTokensPropertiesTask.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 Toast Inc. + * + * 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.toasttab.gradle.testkit + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.util.Properties + +@CacheableTask +abstract class WriteTokensPropertiesTask : DefaultTask() { + @get:Input + abstract val tokens: MapProperty + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @TaskAction + fun write() { + val props = Properties() + for ((key, value) in tokens.get()) { + props.setProperty(key, value) + } + + val file = outputFile.get().asFile + file.parentFile.mkdirs() + file.outputStream().use { props.store(it, null) } + } +} diff --git a/testkit-plugin/src/test/kotlin/com/toasttab/gradle/testkit/TestkitPluginIntegrationTest.kt b/testkit-plugin/src/test/kotlin/com/toasttab/gradle/testkit/TestkitPluginIntegrationTest.kt index 875b8f0..fefcd31 100644 --- a/testkit-plugin/src/test/kotlin/com/toasttab/gradle/testkit/TestkitPluginIntegrationTest.kt +++ b/testkit-plugin/src/test/kotlin/com/toasttab/gradle/testkit/TestkitPluginIntegrationTest.kt @@ -21,9 +21,10 @@ import org.junit.jupiter.api.io.TempDir import strikt.api.expectThat import strikt.assertions.isEqualTo import java.nio.file.Path +import java.util.Properties import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.copyToRecursively -import kotlin.io.path.readText +import kotlin.io.path.inputStream class TestkitPluginIntegrationTest { @TempDir @@ -31,7 +32,7 @@ class TestkitPluginIntegrationTest { @OptIn(ExperimentalPathApi::class) @Test - fun filtering() { + fun `tokens properties file is written with user and built-in tokens`() { Path.of(System.getProperty("test-projects")).copyToRecursively(target = dir, followLinks = false, overwrite = false) val projectDir = dir.resolve("TestkitPluginIntegrationTest/filtering") @@ -40,11 +41,13 @@ class TestkitPluginIntegrationTest { .create() .withProjectDir(projectDir.toFile()) .withPluginClasspath() - .withArguments("test") + .withArguments("writeTestkitTokens") .build() - val data = projectDir.resolve("build/test-projects/test-project/foo").readText().trim() + val props = Properties() + projectDir.resolve("build/testkit/tokens.properties").inputStream().use { props.load(it) } - expectThat(data).isEqualTo("hello world!") + expectThat(props.getProperty("VALUE")).isEqualTo("world!") + expectThat(props.getProperty("VERSION")).isEqualTo("1.0") } }