From 477de83e7ee533cd1aeedef2912d63688ae28447 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 19:23:00 +0800 Subject: [PATCH 01/11] Use @ParameterizedTest in DocCodeSnippetTest --- .../plugins/shadow/DocCodeSnippetTest.kt | 41 ++++++++++--------- .../shadow/snippet/SnippetExecutable.kt | 2 + 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index d9b271dc2..9379ae9b3 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -2,32 +2,35 @@ package com.github.jengelman.gradle.plugins.shadow import com.github.jengelman.gradle.plugins.shadow.snippet.CodeSnippetExtractor import com.github.jengelman.gradle.plugins.shadow.snippet.DslLang +import com.github.jengelman.gradle.plugins.shadow.snippet.SnippetExecutable import java.nio.file.Path -import kotlin.io.path.createDirectory -import org.junit.jupiter.api.DynamicTest -import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource class DocCodeSnippetTest { - @TestFactory - fun provideDynamicTests(@TempDir root: Path): List { - val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } + @ParameterizedTest(name = "{0}") + @MethodSource("snippets") + fun test(executable: SnippetExecutable, @TempDir tempDir: Path) { + executable.tempDir = tempDir + executable.execute() + } - check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } - check(langExecutables.size == DslLang.entries.size) { - "We must provide build script snippets for all languages." - } - check(langExecutables.map { it.size }.distinct().size == 1) { - "All languages must have the same number of code snippets." - } + companion object { + @JvmStatic + fun snippets(): List { + val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } - return langExecutables.flatten().map { - val dirName = it.displayName.replace(nonAlphanumeric, "_") - it.tempDir = root.resolve(dirName).createDirectory() - DynamicTest.dynamicTest(it.displayName, it) + check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } + check(langExecutables.size == DslLang.entries.size) { + "We must provide build script snippets for all languages." + } + check(langExecutables.map { it.size }.distinct().size == 1) { + "All languages must have the same number of code snippets." + } + + return langExecutables.flatten() } } } - -private val nonAlphanumeric = "[^a-zA-Z0-9]".toRegex() diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 7423b312d..4b51023d9 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -25,6 +25,8 @@ sealed class SnippetExecutable : Executable { abstract val displayName: String abstract val exceptionTransformer: (Throwable) -> Throwable + override fun toString(): String = displayName + lateinit var tempDir: Path override fun execute() { From 74a5556ffe5c5aeee0168b802dd010cc591dcb67 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 19:27:02 +0800 Subject: [PATCH 02/11] Clean up tempDir and Executable from SnippetExecutable --- .../gradle/plugins/shadow/DocCodeSnippetTest.kt | 6 +++--- .../plugins/shadow/snippet/SnippetExecutable.kt | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index 9379ae9b3..eedcb2df5 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -13,14 +13,14 @@ class DocCodeSnippetTest { @ParameterizedTest(name = "{0}") @MethodSource("snippets") fun test(executable: SnippetExecutable, @TempDir tempDir: Path) { - executable.tempDir = tempDir - executable.execute() + executable.execute(tempDir) } companion object { @JvmStatic fun snippets(): List { - val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } + val langExecutables = + DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } check(langExecutables.size == DslLang.entries.size) { diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 4b51023d9..86953d9de 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -11,9 +11,8 @@ import kotlin.io.path.createFile import kotlin.io.path.outputStream import kotlin.io.path.writeText import org.gradle.testkit.runner.UnexpectedBuildFailure -import org.junit.jupiter.api.function.Executable -sealed class SnippetExecutable : Executable { +sealed class SnippetExecutable { abstract val lang: DslLang abstract val buildScriptName: String abstract val pluginsBlock: String @@ -27,17 +26,15 @@ sealed class SnippetExecutable : Executable { override fun toString(): String = displayName - lateinit var tempDir: Path - - override fun execute() { + fun execute(projectRoot: Path) { try { - execute(tempDir, snippet) + executeSnippet(projectRoot, snippet) } catch (t: Throwable) { throw exceptionTransformer(t) } } - private fun execute(projectRoot: Path, snippet: String) { + private fun executeSnippet(projectRoot: Path, snippet: String) { projectRoot .resolve("settings.gradle") .writeText( From 39521e402447818fae2d8e5a840f82f35ee8e962 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 19:39:09 +0800 Subject: [PATCH 03/11] Refine documentation test arguments --- .../gradle/plugins/shadow/DocCodeSnippetTest.kt | 12 +++++++----- .../plugins/shadow/snippet/SnippetExecutable.kt | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index eedcb2df5..d3bd20fe3 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -4,8 +4,11 @@ import com.github.jengelman.gradle.plugins.shadow.snippet.CodeSnippetExtractor import com.github.jengelman.gradle.plugins.shadow.snippet.DslLang import com.github.jengelman.gradle.plugins.shadow.snippet.SnippetExecutable import java.nio.file.Path +import org.junit.jupiter.api.Named.named import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource class DocCodeSnippetTest { @@ -18,19 +21,18 @@ class DocCodeSnippetTest { companion object { @JvmStatic - fun snippets(): List { + fun snippets(): List { val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } - check(langExecutables.size == DslLang.entries.size) { - "We must provide build script snippets for all languages." - } check(langExecutables.map { it.size }.distinct().size == 1) { "All languages must have the same number of code snippets." } - return langExecutables.flatten() + return langExecutables.flatten().map { executable -> + arguments(named(executable.displayName, executable)) + } } } } diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 86953d9de..80c0e6a54 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -24,8 +24,6 @@ sealed class SnippetExecutable { abstract val displayName: String abstract val exceptionTransformer: (Throwable) -> Throwable - override fun toString(): String = displayName - fun execute(projectRoot: Path) { try { executeSnippet(projectRoot, snippet) From 00b7983c2a73a940702715ce4d2e9813b0708e12 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:01:49 +0800 Subject: [PATCH 04/11] Revert Arguments --- .../gradle/plugins/shadow/DocCodeSnippetTest.kt | 9 ++------- .../gradle/plugins/shadow/snippet/SnippetExecutable.kt | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index d3bd20fe3..08862054e 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -4,11 +4,8 @@ import com.github.jengelman.gradle.plugins.shadow.snippet.CodeSnippetExtractor import com.github.jengelman.gradle.plugins.shadow.snippet.DslLang import com.github.jengelman.gradle.plugins.shadow.snippet.SnippetExecutable import java.nio.file.Path -import org.junit.jupiter.api.Named.named import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource class DocCodeSnippetTest { @@ -21,7 +18,7 @@ class DocCodeSnippetTest { companion object { @JvmStatic - fun snippets(): List { + fun snippets(): List { val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } @@ -30,9 +27,7 @@ class DocCodeSnippetTest { "All languages must have the same number of code snippets." } - return langExecutables.flatten().map { executable -> - arguments(named(executable.displayName, executable)) - } + return langExecutables.flatten() } } } diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 80c0e6a54..86953d9de 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -24,6 +24,8 @@ sealed class SnippetExecutable { abstract val displayName: String abstract val exceptionTransformer: (Throwable) -> Throwable + override fun toString(): String = displayName + fun execute(projectRoot: Path) { try { executeSnippet(projectRoot, snippet) From 88e07984bc13e43c41446b5e8a17cbfe4cbc0d0d Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:07:16 +0800 Subject: [PATCH 05/11] Simplify snippet failure locations --- .../plugins/shadow/snippet/CodeSnippetExtractor.kt | 12 ++++++------ .../plugins/shadow/snippet/GroovyBuildExecutable.kt | 2 +- .../plugins/shadow/snippet/KotlinBuildExecutable.kt | 2 +- .../plugins/shadow/snippet/SnippetExecutable.kt | 13 ++++++++----- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt index 859117a26..3d6c41955 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt @@ -22,12 +22,12 @@ object CodeSnippetExtractor { private fun createExecutables(lang: DslLang, markdownPath: Path): List { val relativeDocPath = markdownPath.relativeTo(docRoot).toString() return createSnippets(markdownPath.readText(), lang).map { (lineNumber, snippet) -> - SnippetExecutable.create(lang, snippet, "$relativeDocPath:$lineNumber") { cause -> - RuntimeException( - "The error line in the doc is near ${markdownPath.toUri()}:$lineNumber\n\n${cause.message}", - cause, - ) - } + SnippetExecutable.create( + lang = lang, + snippet = snippet, + testName = "$relativeDocPath:$lineNumber", + sourceLocation = "${markdownPath.toUri()}:$lineNumber", + ) } } diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt index c89775987..a9b030af0 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt @@ -3,7 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.snippet class GroovyBuildExecutable( override val snippet: String, override val displayName: String, - override val exceptionTransformer: (Throwable) -> Throwable, + override val sourceLocation: String, ) : SnippetExecutable() { override val lang: DslLang = DslLang.Groovy diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt index 4cf702695..b927f2079 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt @@ -3,7 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.snippet class KotlinBuildExecutable( override val snippet: String, override val displayName: String, - override val exceptionTransformer: (Throwable) -> Throwable, + override val sourceLocation: String, ) : SnippetExecutable() { override val lang: DslLang = DslLang.Kotlin diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 86953d9de..7b7dca795 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -22,7 +22,7 @@ sealed class SnippetExecutable { /** Unique name for the test, formatted as `publishing/README.md:10`. */ abstract val displayName: String - abstract val exceptionTransformer: (Throwable) -> Throwable + abstract val sourceLocation: String override fun toString(): String = displayName @@ -30,7 +30,10 @@ sealed class SnippetExecutable { try { executeSnippet(projectRoot, snippet) } catch (t: Throwable) { - throw exceptionTransformer(t) + throw RuntimeException( + "The error line in the doc is near $sourceLocation\n\n${t.message}", + t, + ) } } @@ -152,11 +155,11 @@ sealed class SnippetExecutable { lang: DslLang, snippet: String, testName: String, - exceptionTransformer: (Throwable) -> Throwable, + sourceLocation: String, ): SnippetExecutable = when (lang) { - DslLang.Groovy -> GroovyBuildExecutable(snippet, testName, exceptionTransformer) - DslLang.Kotlin -> KotlinBuildExecutable(snippet, testName, exceptionTransformer) + DslLang.Groovy -> GroovyBuildExecutable(snippet, testName, sourceLocation) + DslLang.Kotlin -> KotlinBuildExecutable(snippet, testName, sourceLocation) } } } From 99e5d015cb5c5e1852863ff6268594fe2d72e20e Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:13:58 +0800 Subject: [PATCH 06/11] Use interface for snippet executables --- .../plugins/shadow/DocCodeSnippetTest.kt | 9 +++++++-- .../shadow/snippet/GroovyBuildExecutable.kt | 2 +- .../shadow/snippet/KotlinBuildExecutable.kt | 2 +- .../shadow/snippet/SnippetExecutable.kt | 20 ++++++++----------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index 08862054e..d3bd20fe3 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -4,8 +4,11 @@ import com.github.jengelman.gradle.plugins.shadow.snippet.CodeSnippetExtractor import com.github.jengelman.gradle.plugins.shadow.snippet.DslLang import com.github.jengelman.gradle.plugins.shadow.snippet.SnippetExecutable import java.nio.file.Path +import org.junit.jupiter.api.Named.named import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource class DocCodeSnippetTest { @@ -18,7 +21,7 @@ class DocCodeSnippetTest { companion object { @JvmStatic - fun snippets(): List { + fun snippets(): List { val langExecutables = DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } @@ -27,7 +30,9 @@ class DocCodeSnippetTest { "All languages must have the same number of code snippets." } - return langExecutables.flatten() + return langExecutables.flatten().map { executable -> + arguments(named(executable.displayName, executable)) + } } } } diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt index a9b030af0..e75821e61 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt @@ -4,7 +4,7 @@ class GroovyBuildExecutable( override val snippet: String, override val displayName: String, override val sourceLocation: String, -) : SnippetExecutable() { +) : SnippetExecutable { override val lang: DslLang = DslLang.Groovy diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt index b927f2079..783f4bf68 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt @@ -4,7 +4,7 @@ class KotlinBuildExecutable( override val snippet: String, override val displayName: String, override val sourceLocation: String, -) : SnippetExecutable() { +) : SnippetExecutable { override val lang: DslLang = DslLang.Kotlin diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 7b7dca795..08c0908ed 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -12,19 +12,15 @@ import kotlin.io.path.outputStream import kotlin.io.path.writeText import org.gradle.testkit.runner.UnexpectedBuildFailure -sealed class SnippetExecutable { - abstract val lang: DslLang - abstract val buildScriptName: String - abstract val pluginsBlock: String - abstract val assembleDependsOn: String - - abstract val snippet: String - +sealed interface SnippetExecutable { + val lang: DslLang + val buildScriptName: String + val pluginsBlock: String + val assembleDependsOn: String + val snippet: String /** Unique name for the test, formatted as `publishing/README.md:10`. */ - abstract val displayName: String - abstract val sourceLocation: String - - override fun toString(): String = displayName + val displayName: String + val sourceLocation: String fun execute(projectRoot: Path) { try { From e339029c9e560b9618a9c73c63906f67de071f87 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:31:21 +0800 Subject: [PATCH 07/11] Use top-level snippet extraction helpers --- .../plugins/shadow/DocCodeSnippetTest.kt | 7 +-- .../shadow/snippet/CodeSnippetExtractor.kt | 60 +++++++++---------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index d3bd20fe3..6900a05ff 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -1,8 +1,8 @@ package com.github.jengelman.gradle.plugins.shadow -import com.github.jengelman.gradle.plugins.shadow.snippet.CodeSnippetExtractor import com.github.jengelman.gradle.plugins.shadow.snippet.DslLang import com.github.jengelman.gradle.plugins.shadow.snippet.SnippetExecutable +import com.github.jengelman.gradle.plugins.shadow.snippet.extractCodeSnippets import java.nio.file.Path import org.junit.jupiter.api.Named.named import org.junit.jupiter.api.io.TempDir @@ -19,11 +19,10 @@ class DocCodeSnippetTest { executable.execute(tempDir) } - companion object { + private companion object { @JvmStatic fun snippets(): List { - val langExecutables = - DslLang.entries.map { executor -> CodeSnippetExtractor.extract(executor) } + val langExecutables = DslLang.entries.map { executor -> extractCodeSnippets(executor) } check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } check(langExecutables.map { it.size }.distinct().size == 1) { diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt index 3d6c41955..ed0d621d4 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt @@ -9,44 +9,42 @@ import kotlin.io.path.readText import kotlin.io.path.relativeTo import kotlin.io.path.walk -object CodeSnippetExtractor { - private val docRoot = Path(DOCS_DIR) +private val docRoot = Path(DOCS_DIR) - private val markdownPaths = - docRoot.walk().filter { it.name.endsWith(".md", ignoreCase = true) }.toList() +private val markdownPaths = + docRoot.walk().filter { it.name.endsWith(".md", ignoreCase = true) }.toList() - fun extract(lang: DslLang): List { - return markdownPaths.flatMap { path -> createExecutables(lang, path) } - } +fun extractCodeSnippets(lang: DslLang): List { + return markdownPaths.flatMap { path -> createExecutables(lang, path) } +} - private fun createExecutables(lang: DslLang, markdownPath: Path): List { - val relativeDocPath = markdownPath.relativeTo(docRoot).toString() - return createSnippets(markdownPath.readText(), lang).map { (lineNumber, snippet) -> - SnippetExecutable.create( - lang = lang, - snippet = snippet, - testName = "$relativeDocPath:$lineNumber", - sourceLocation = "${markdownPath.toUri()}:$lineNumber", - ) - } +private fun createExecutables(lang: DslLang, markdownPath: Path): List { + val relativeDocPath = markdownPath.relativeTo(docRoot).toString() + return createSnippets(markdownPath.readText(), lang).map { (lineNumber, snippet) -> + SnippetExecutable.create( + lang = lang, + snippet = snippet, + testName = "$relativeDocPath:$lineNumber", + sourceLocation = "${markdownPath.toUri()}:$lineNumber", + ) } +} - private fun createSnippets(source: String, lang: DslLang) = buildMap { - val pattern = Pattern.compile("(?ims) {4}```${lang}\n(.*?)\n {4}```") - val matcher = pattern.matcher(source) +private fun createSnippets(source: String, lang: DslLang) = buildMap { + val pattern = Pattern.compile("(?ims) {4}```${lang}\n(.*?)\n {4}```") + val matcher = pattern.matcher(source) - while (matcher.find()) { - val line = source.lineNumberAt(matcher.start()) - val code = matcher.group(1) - put(line, code) - } + while (matcher.find()) { + val line = source.lineNumberAt(matcher.start()) + val code = matcher.group(1) + put(line, code) } +} - private fun String.lineNumberAt(index: Int): Int { - var line = 1 - for (i in 0 until index.coerceAtMost(length)) { - if (this[i] == '\n') line++ - } - return line +private fun String.lineNumberAt(index: Int): Int { + var line = 1 + for (i in 0 until index.coerceAtMost(length)) { + if (this[i] == '\n') line++ } + return line } From 079f17928e9ce0c0fb5cc5246f51e5731fb0d1aa Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:33:14 +0800 Subject: [PATCH 08/11] Inline executeSnippet --- .../shadow/snippet/SnippetExecutable.kt | 128 +++++++++--------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index 08c0908ed..ec64af6aa 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -24,20 +24,10 @@ sealed interface SnippetExecutable { fun execute(projectRoot: Path) { try { - executeSnippet(projectRoot, snippet) - } catch (t: Throwable) { - throw RuntimeException( - "The error line in the doc is near $sourceLocation\n\n${t.message}", - t, - ) - } - } - - private fun executeSnippet(projectRoot: Path, snippet: String) { - projectRoot - .resolve("settings.gradle") - .writeText( - """ + projectRoot + .resolve("settings.gradle") + .writeText( + """ |gradle.beforeProject { p -> | // Snippet version placeholders resolve to '+', so avoid frequent remote version checks. | p.buildscript.configurations.configureEach { @@ -62,68 +52,74 @@ sealed interface SnippetExecutable { |enableFeaturePreview 'STABLE_CONFIGURATION_CACHE' |enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS' """ - .trimMargin() - ) + .trimMargin() + ) - val apiScript = buildString { - appendLine(pluginsBlock) - append(assembleDependsOn) - } - projectRoot.addSubProject("api", apiScript) + val apiScript = buildString { + appendLine(pluginsBlock) + append(assembleDependsOn) + } + projectRoot.addSubProject("api", apiScript) - val (imports, withoutImports) = importsExtractor(snippet) - val mainScript = buildString { - appendLine(imports) - // All buildscript {} blocks must appear before any plugins {} blocks in the script. - if (withoutImports.contains("buildscript {")) { - appendLine(withoutImports) - } else { - if (!withoutImports.contains("plugins {")) { - appendLine(pluginsBlock) + val (imports, withoutImports) = importsExtractor(snippet) + val mainScript = buildString { + appendLine(imports) + // All buildscript {} blocks must appear before any plugins {} blocks in the script. + if (withoutImports.contains("buildscript {")) { + appendLine(withoutImports) + } else { + if (!withoutImports.contains("plugins {")) { + appendLine(pluginsBlock) + } + appendLine(withoutImports) } - appendLine(withoutImports) } - } - .trimIndent() - projectRoot.addSubProject("main", mainScript + assembleDependsOn) - projectRoot.resolve("main/foo.jar").createFile().also { - // Dummy JAR file to ensure the project can be built. - JarOutputStream(it.outputStream()).use {} - } - projectRoot.resolve("main/bar.jar").createFile().also { - // Dummy JAR file to ensure the project can be built. - JarOutputStream(it.outputStream()).use {} - } + .trimIndent() + projectRoot.addSubProject("main", mainScript + assembleDependsOn) + projectRoot.resolve("main/foo.jar").createFile().also { + // Dummy JAR file to ensure the project can be built. + JarOutputStream(it.outputStream()).use {} + } + projectRoot.resolve("main/bar.jar").createFile().also { + // Dummy JAR file to ensure the project can be built. + JarOutputStream(it.outputStream()).use {} + } - // Script-defined classes (e.g., inline custom ResourceTransformer) are not supported by - // CC/IP because transient script classloaders cannot be serialized. - val runnerArgs = - if (withoutImports.contains("class ")) { - commonGradleArgs.filterNot { - it == "--configuration-cache" || it.contains("isolated-projects") + // Script-defined classes (e.g., inline custom ResourceTransformer) are not supported by + // CC/IP because transient script classloaders cannot be serialized. + val runnerArgs = + if (withoutImports.contains("class ")) { + commonGradleArgs.filterNot { + it == "--configuration-cache" || it.contains("isolated-projects") + } + } else { + commonGradleArgs.toList() } - } else { - commonGradleArgs.toList() - } - try { - gradleRunner(projectDir = projectRoot, arguments = runnerArgs + "build") - .build() - .assertNoDeprecationWarnings() - } catch (t: Throwable) { - val buildOutput = (t as? UnexpectedBuildFailure)?.buildResult?.output - val message = buildString { - appendLine("--- Snippet ---") - appendLine() - appendLine(mainScript) - if (!buildOutput.isNullOrBlank()) { + try { + gradleRunner(projectDir = projectRoot, arguments = runnerArgs + "build") + .build() + .assertNoDeprecationWarnings() + } catch (t: Throwable) { + val buildOutput = (t as? UnexpectedBuildFailure)?.buildResult?.output + val message = buildString { + appendLine("--- Snippet ---") appendLine() - appendLine("--- Gradle Build Output ---") - appendLine() - appendLine(buildOutput.trim()) + appendLine(mainScript) + if (!buildOutput.isNullOrBlank()) { + appendLine() + appendLine("--- Gradle Build Output ---") + appendLine() + appendLine(buildOutput.trim()) + } } + throw RuntimeException(message, t) } - throw RuntimeException(message, t) + } catch (t: Throwable) { + throw RuntimeException( + "The error line in the doc is near $sourceLocation\n\n${t.message}", + t, + ) } } From c43e13c98d424a367b0476059602585090fb1374 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:40:49 +0800 Subject: [PATCH 09/11] Clean up snippet execution --- .../shadow/snippet/GroovyBuildExecutable.kt | 3 - .../shadow/snippet/KotlinBuildExecutable.kt | 3 - .../shadow/snippet/SnippetExecutable.kt | 57 ++++++++++--------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt index e75821e61..32a103108 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt @@ -5,9 +5,6 @@ class GroovyBuildExecutable( override val displayName: String, override val sourceLocation: String, ) : SnippetExecutable { - - override val lang: DslLang = DslLang.Groovy - override val buildScriptName: String = "build.gradle" override val pluginsBlock: String = diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt index 783f4bf68..a9164a10e 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt @@ -5,9 +5,6 @@ class KotlinBuildExecutable( override val displayName: String, override val sourceLocation: String, ) : SnippetExecutable { - - override val lang: DslLang = DslLang.Kotlin - override val buildScriptName: String = "build.gradle.kts" override val pluginsBlock: String = diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index ec64af6aa..ebc7a4b25 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -7,13 +7,11 @@ import com.github.jengelman.gradle.plugins.shadow.testkit.gradleRunner import java.nio.file.Path import java.util.jar.JarOutputStream import kotlin.io.path.createDirectory -import kotlin.io.path.createFile import kotlin.io.path.outputStream import kotlin.io.path.writeText import org.gradle.testkit.runner.UnexpectedBuildFailure sealed interface SnippetExecutable { - val lang: DslLang val buildScriptName: String val pluginsBlock: String val assembleDependsOn: String @@ -23,6 +21,8 @@ sealed interface SnippetExecutable { val sourceLocation: String fun execute(projectRoot: Path) { + var generatedMainScript: String? = null + var gradleBuildOutput: String? = null try { projectRoot .resolve("settings.gradle") @@ -61,7 +61,7 @@ sealed interface SnippetExecutable { } projectRoot.addSubProject("api", apiScript) - val (imports, withoutImports) = importsExtractor(snippet) + val (imports, withoutImports) = extractImports() val mainScript = buildString { appendLine(imports) // All buildscript {} blocks must appear before any plugins {} blocks in the script. @@ -75,14 +75,11 @@ sealed interface SnippetExecutable { } } .trimIndent() + generatedMainScript = mainScript projectRoot.addSubProject("main", mainScript + assembleDependsOn) - projectRoot.resolve("main/foo.jar").createFile().also { + listOf("foo.jar", "bar.jar").forEach { name -> // Dummy JAR file to ensure the project can be built. - JarOutputStream(it.outputStream()).use {} - } - projectRoot.resolve("main/bar.jar").createFile().also { - // Dummy JAR file to ensure the project can be built. - JarOutputStream(it.outputStream()).use {} + JarOutputStream(projectRoot.resolve("main/$name").outputStream()).use {} } // Script-defined classes (e.g., inline custom ResourceTransformer) are not supported by @@ -96,28 +93,34 @@ sealed interface SnippetExecutable { commonGradleArgs.toList() } - try { - gradleRunner(projectDir = projectRoot, arguments = runnerArgs + "build") - .build() - .assertNoDeprecationWarnings() - } catch (t: Throwable) { - val buildOutput = (t as? UnexpectedBuildFailure)?.buildResult?.output - val message = buildString { - appendLine("--- Snippet ---") - appendLine() - appendLine(mainScript) + gradleRunner(projectDir = projectRoot, arguments = runnerArgs + "build") + .build() + .also { gradleBuildOutput = it.output } + .assertNoDeprecationWarnings() + } catch (t: Throwable) { + val buildOutput = (t as? UnexpectedBuildFailure)?.buildResult?.output ?: gradleBuildOutput + throw RuntimeException( + buildString { + append("The error line in the doc is near $sourceLocation") + if (generatedMainScript != null) { + appendLine() + appendLine() + appendLine("--- Snippet ---") + appendLine() + append(generatedMainScript) + } if (!buildOutput.isNullOrBlank()) { + appendLine() appendLine() appendLine("--- Gradle Build Output ---") appendLine() - appendLine(buildOutput.trim()) + append(buildOutput.trim()) + } else if (!t.message.isNullOrBlank()) { + appendLine() + appendLine() + append(t.message) } - } - throw RuntimeException(message, t) - } - } catch (t: Throwable) { - throw RuntimeException( - "The error line in the doc is near $sourceLocation\n\n${t.message}", + }, t, ) } @@ -127,7 +130,7 @@ sealed interface SnippetExecutable { resolve(project).createDirectory().resolve(buildScriptName).writeText(buildScriptText) } - private fun importsExtractor(snippet: String): Pair { + private fun extractImports(): Pair { val imports = StringBuilder() val withoutImports = StringBuilder() From 189c7ae3ce85f4101f49492627077e6a3232ab9f Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 20:50:07 +0800 Subject: [PATCH 10/11] Simplify code snippet extraction --- .../plugins/shadow/DocCodeSnippetTest.kt | 2 +- .../shadow/snippet/CodeSnippetExtractor.kt | 53 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index 6900a05ff..34a5c50af 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -22,7 +22,7 @@ class DocCodeSnippetTest { private companion object { @JvmStatic fun snippets(): List { - val langExecutables = DslLang.entries.map { executor -> extractCodeSnippets(executor) } + val langExecutables = DslLang.entries.map(DslLang::extractCodeSnippets) check(langExecutables.sumOf { it.size } > 0) { "No code snippets found." } check(langExecutables.map { it.size }.distinct().size == 1) { diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt index ed0d621d4..5c6a95894 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/CodeSnippetExtractor.kt @@ -1,7 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.snippet import com.github.jengelman.gradle.plugins.shadow.DocumentTestBuildConfig.DOCS_DIR -import java.nio.file.Path import java.util.regex.Pattern import kotlin.io.path.Path import kotlin.io.path.name @@ -11,34 +10,30 @@ import kotlin.io.path.walk private val docRoot = Path(DOCS_DIR) -private val markdownPaths = - docRoot.walk().filter { it.name.endsWith(".md", ignoreCase = true) }.toList() - -fun extractCodeSnippets(lang: DslLang): List { - return markdownPaths.flatMap { path -> createExecutables(lang, path) } -} - -private fun createExecutables(lang: DslLang, markdownPath: Path): List { - val relativeDocPath = markdownPath.relativeTo(docRoot).toString() - return createSnippets(markdownPath.readText(), lang).map { (lineNumber, snippet) -> - SnippetExecutable.create( - lang = lang, - snippet = snippet, - testName = "$relativeDocPath:$lineNumber", - sourceLocation = "${markdownPath.toUri()}:$lineNumber", - ) - } -} - -private fun createSnippets(source: String, lang: DslLang) = buildMap { - val pattern = Pattern.compile("(?ims) {4}```${lang}\n(.*?)\n {4}```") - val matcher = pattern.matcher(source) - - while (matcher.find()) { - val line = source.lineNumberAt(matcher.start()) - val code = matcher.group(1) - put(line, code) - } +fun DslLang.extractCodeSnippets(): List { + val lang = this + return docRoot + .walk() + .filter { it.name.endsWith(".md", ignoreCase = true) } + .flatMap { path -> + val source = path.readText() + val matcher = Pattern.compile("(?ims) {4}```${lang}\n(.*?)\n {4}```").matcher(source) + + buildList { + while (matcher.find()) { + val lineNumber = source.lineNumberAt(matcher.start()) + add( + SnippetExecutable.create( + lang = lang, + snippet = matcher.group(1), + testName = "${path.relativeTo(docRoot)}:$lineNumber", + sourceLocation = "${path.toUri()}:$lineNumber", + ) + ) + } + } + } + .toList() } private fun String.lineNumberAt(index: Int): Int { From 16302c23ba4e3213422870a5e743b8b62caf947e Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 13 Aug 2026 21:16:10 +0800 Subject: [PATCH 11/11] Share snippet plugins block --- .../plugins/shadow/snippet/GroovyBuildExecutable.kt | 9 --------- .../plugins/shadow/snippet/KotlinBuildExecutable.kt | 9 --------- .../gradle/plugins/shadow/snippet/SnippetExecutable.kt | 10 ++++++++-- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt index 32a103108..36b6bf7ec 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/GroovyBuildExecutable.kt @@ -7,15 +7,6 @@ class GroovyBuildExecutable( ) : SnippetExecutable { override val buildScriptName: String = "build.gradle" - override val pluginsBlock: String = - """ - |plugins { - | id 'java' - | id 'com.gradleup.shadow' - |} - """ - .trimMargin() - override val assembleDependsOn: String = """ |tasks.named('assemble') { diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt index a9164a10e..bce103673 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/KotlinBuildExecutable.kt @@ -7,15 +7,6 @@ class KotlinBuildExecutable( ) : SnippetExecutable { override val buildScriptName: String = "build.gradle.kts" - override val pluginsBlock: String = - """ - |plugins { - | java - | id("com.gradleup.shadow") - |} - """ - .trimMargin() - override val assembleDependsOn: String = """ |tasks.named("assemble") { diff --git a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt index ebc7a4b25..c22fc9515 100644 --- a/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt +++ b/src/documentTest/kotlin/com/github/jengelman/gradle/plugins/shadow/snippet/SnippetExecutable.kt @@ -13,7 +13,6 @@ import org.gradle.testkit.runner.UnexpectedBuildFailure sealed interface SnippetExecutable { val buildScriptName: String - val pluginsBlock: String val assembleDependsOn: String val snippet: String /** Unique name for the test, formatted as `publishing/README.md:10`. */ @@ -54,7 +53,14 @@ sealed interface SnippetExecutable { """ .trimMargin() ) - + val pluginsBlock = + """ + |plugins { + | id("java") + | id("com.gradleup.shadow") + |} + """ + .trimMargin() val apiScript = buildString { appendLine(pluginsBlock) append(assembleDependsOn)