├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── dependency-review-action.yml │ ├── dependency-submission.yml │ ├── gradle.yml │ └── submit-dependency-graph.yml ├── .gitignore ├── .teamcity ├── Project.kt ├── pom.xml └── settings.kts ├── LICENSE ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── exemplar.java-conventions.gradle.kts │ └── exemplar.publishing-conventions.gradle.kts ├── docs ├── README.adoc ├── _config.yml ├── build.gradle.kts └── src │ └── test │ └── java │ └── org │ └── gradle │ └── exemplar │ └── ReadmeTest.java ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples-check ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── org │ │ └── gradle │ │ └── exemplar │ │ ├── executor │ │ ├── CliCommandExecutor.java │ │ ├── CommandExecutionResult.java │ │ ├── CommandExecutor.java │ │ ├── ExecutionMetadata.java │ │ └── GradleRunnerCommandExecutor.java │ │ └── test │ │ ├── normalizer │ │ ├── AsciidoctorAnnotationOutputNormalizer.java │ │ ├── FileSeparatorOutputNormalizer.java │ │ ├── GradleOutputNormalizer.java │ │ ├── JavaObjectSerializationOutputNormalizer.java │ │ ├── LeadingNewLineOutputNormalizer.java │ │ ├── OutputNormalizer.java │ │ ├── StripTrailingOutputNormalizer.java │ │ ├── TrailingNewLineOutputNormalizer.java │ │ └── WorkingDirectoryOutputNormalizer.java │ │ ├── rule │ │ ├── Sample.java │ │ └── UsesSample.java │ │ ├── runner │ │ ├── EmbeddedSamplesRunner.java │ │ ├── GradleEmbeddedSamplesRunner.java │ │ ├── GradleSamplesRunner.java │ │ ├── SampleModifier.java │ │ ├── SampleModifiers.java │ │ ├── SamplesOutputNormalizers.java │ │ ├── SamplesRoot.java │ │ ├── SamplesRunner.java │ │ └── Transformer.java │ │ └── verifier │ │ ├── AnyOrderLineSegmentedOutputVerifier.java │ │ ├── OutputVerifier.java │ │ └── StrictOrderLineSegmentedOutputVerifier.java │ └── test │ ├── docs │ └── embedded-test.adoc │ ├── groovy │ └── org │ │ └── gradle │ │ └── exemplar │ │ └── test │ │ ├── normalizer │ │ ├── AsciidoctorAnnotationOutputNormalizerTest.groovy │ │ ├── FileSeparatorOutputNormalizerTest.groovy │ │ ├── GradleOutputNormalizerTest.groovy │ │ ├── LeadingNewLineOutputNormalizerTest.groovy │ │ ├── StripTrailingOutputNormalizerTest.groovy │ │ ├── TrailingNewLineOutputNormalizerTest.groovy │ │ └── WorkingDirectoryOutputNormalizerTest.groovy │ │ ├── rule │ │ └── SampleTest.groovy │ │ ├── runner │ │ ├── BrokenSampleDiscoveryIntegrationTest.groovy │ │ ├── CollectingNotifier.groovy │ │ ├── SamplesRunnerIntegrationTest.groovy │ │ └── SamplesRunnerSadDayIntegrationTest.groovy │ │ └── verifier │ │ ├── AnyOrderLineSegmentedOutputVerifierTest.groovy │ │ └── StrictOrderLineSegmentedOutputVerifierTest.groovy │ ├── java │ └── org │ │ └── gradle │ │ └── exemplar │ │ └── test │ │ └── runner │ │ ├── CliSamplesRunnerIntegrationTest.java │ │ ├── CoveredByTests.java │ │ ├── EmbeddedSamplesRunnerIntegrationTest.java │ │ ├── GradleSamplesRunnerIntegrationTest.java │ │ ├── SampleModifierIntegrationTest.java │ │ └── modifiers │ │ └── ExtraCommandArgumentsSampleModifier.java │ ├── resources │ └── broken │ │ ├── command │ │ └── broken-command.sample.conf │ │ └── output │ │ ├── broken-output.sample.conf │ │ └── sample.out │ └── samples │ ├── cli-with-working-directory-and-change-directory │ └── multi-step │ │ ├── multi-step.sample.conf │ │ ├── sample.out │ │ ├── sample.sh │ │ └── workDir │ │ └── .placeholder │ ├── cli-with-working-directory │ └── multi-step │ │ ├── multi-step.sample.conf │ │ ├── sample.out │ │ ├── sample.sh │ │ └── workDir │ │ └── .placeholder │ ├── cli │ ├── multi-step │ │ ├── multi-step.sample.conf │ │ ├── sample.out │ │ └── sample.sh │ └── quickstart │ │ ├── quickstart.sample.conf │ │ ├── quickstart.sample.out │ │ └── sample.sh │ ├── customization │ └── customization-sample │ │ ├── build.gradle │ │ ├── hello.sample.conf │ │ └── hello.sample.out │ └── gradle │ ├── basic-sample │ ├── build.gradle │ ├── hello.sample.conf │ └── hello.sample.out │ ├── build-init-sample │ ├── build-init.sample.conf │ └── sample.out │ ├── composite-sample │ └── basic │ │ ├── composite │ │ ├── build.gradle │ │ └── settings.gradle │ │ ├── compositeBuildsBasicCli.sample.conf │ │ ├── compositeBuildsBasicCli.sample.out │ │ ├── my-app │ │ ├── build.gradle │ │ ├── settings-composite.gradle │ │ ├── settings.gradle │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── sample │ │ │ └── myapp │ │ │ └── Main.java │ │ └── my-utils │ │ ├── build.gradle │ │ ├── number-utils │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── sample │ │ │ └── numberutils │ │ │ └── Numbers.java │ │ ├── settings.gradle │ │ └── string-utils │ │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── sample │ │ └── stringutils │ │ └── Strings.java │ ├── dual-dsl-sample │ ├── groovy │ │ ├── build.gradle │ │ └── settings.gradle │ ├── helpTask.out │ ├── helpTask.sample.conf │ └── kotlin │ │ ├── build.gradle.kts │ │ └── settings.gradle.kts │ └── multi-step-sample │ ├── build.gradle │ ├── incrementalTaskRemovedOutput.out │ ├── incrementalTaskRemovedOutput.sample.conf │ ├── originalInputs.out │ └── settings.gradle ├── samples-discovery ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── org │ │ └── gradle │ │ └── exemplar │ │ ├── InvalidSampleException.java │ │ ├── loader │ │ ├── CommandsParser.java │ │ ├── ConfigUtil.java │ │ ├── SamplesDiscovery.java │ │ └── asciidoctor │ │ │ ├── AsciidoctorCommandsDiscovery.java │ │ │ └── AsciidoctorSamplesDiscovery.java │ │ └── model │ │ ├── Command.java │ │ ├── InvalidSample.java │ │ └── Sample.java │ └── test │ └── groovy │ └── org │ └── gradle │ └── exemplar │ ├── loader │ ├── SamplesDiscoveryTest.groovy │ └── asciidoctor │ │ ├── AsciidoctorCommandsDiscoveryTest.groovy │ │ └── AsciidoctorSamplesDiscoveryTest.groovy │ └── model │ └── CommandsParserTest.groovy └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.yml] 16 | indent_size = 2 17 | 18 | # Markdown files sometimes need trailing whitespaces. 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | In order to foster a more inclusive community, Gradle has adopted the [Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). 4 | 5 | Contributors must follow the Code of Conduct outlined at [https://gradle.org/conduct/](https://gradle.org/conduct/). 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug to help us improve 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Provide a brief summary of the issue in the title above. 10 | - type: textarea 11 | id: expected-behavior 12 | attributes: 13 | label: Expected Behavior 14 | description: Tell us what should happen. 15 | - type: textarea 16 | id: current-behavior 17 | attributes: 18 | label: Current Behavior 19 | description: Tell us what happens instead of the expected behavior. 20 | - type: textarea 21 | id: context 22 | attributes: 23 | label: Context 24 | description: | 25 | How has this issue affected you? What are you trying to accomplish? 26 | Providing context helps us come up with a solution that is most useful in the real world. 27 | - type: textarea 28 | id: steps-to-reproduce 29 | attributes: 30 | label: Steps to Reproduce 31 | description: | 32 | Provide a self-contained example project (as an attached archive or a Github project). 33 | In the rare cases where this is infeasible, we will also accept a detailed set of instructions. 34 | - type: textarea 35 | id: environment 36 | attributes: 37 | label: Your Environment 38 | description: | 39 | Include as many relevant details about the environment you experienced the bug in. 40 | A build scan `https://scans.gradle.com` is ideal. 41 | value: https://scans.gradle.com/s/... 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | labels: 4 | - enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Provide a brief summary of the issue in the title above 10 | - type: textarea 11 | id: expected-behavior 12 | attributes: 13 | label: Expected Behavior 14 | description: Tell us how this feature should work. 15 | - type: textarea 16 | id: current-behavior 17 | attributes: 18 | label: Current Behavior 19 | description: Explain the difference from current behavior. 20 | - type: textarea 21 | id: context 22 | attributes: 23 | label: Context 24 | description: What are you trying to accomplish? Providing context helps us come up with a solution that is most useful in the real world. 25 | - type: textarea 26 | id: additional_context 27 | attributes: 28 | label: Additional Context 29 | description: Provide any other relevant context or information that you think may be relevant for this feature request. 30 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | labels: 17 | - "@dev-productivity" 18 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review-action.yml: -------------------------------------------------------------------------------- 1 | name: Dependency review for pull requests 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | dependency-submission: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: temurin 18 | java-version: 21 19 | 20 | - name: Generate and submit dependency graph 21 | uses: gradle/actions/dependency-submission@v4 22 | 23 | - name: Perform dependency review 24 | uses: actions/dependency-review-action@v4 25 | -------------------------------------------------------------------------------- /.github/workflows/dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Generate and save dependency graph 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-submission: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v4 19 | - name: Setup Java 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | - name: Generate and save dependency graph 25 | uses: gradle/actions/dependency-submission@v4 26 | with: 27 | dependency-graph: generate-and-upload 28 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Build with Gradle 9 | 10 | on: 11 | push: 12 | branches: 13 | - "main" 14 | pull_request: 15 | branches: 16 | - "main" 17 | workflow_dispatch: 18 | merge_group: 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Setup Java 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: '17' 32 | distribution: 'temurin' 33 | - name: Setup Gradle 34 | uses: gradle/actions/setup-gradle@v4 35 | - run: ./gradlew build 36 | -------------------------------------------------------------------------------- /.github/workflows/submit-dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Download and submit dependency graph 2 | 3 | on: 4 | workflow_run: 5 | workflows: ['Generate and save dependency graph'] 6 | types: [completed] 7 | 8 | permissions: 9 | actions: read 10 | contents: write 11 | 12 | jobs: 13 | submit-dependency-graph: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Download and submit dependency graph 17 | uses: gradle/actions/dependency-submission@v4 18 | with: 19 | dependency-graph: download-and-submit 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | 4 | # Ignore Gradle GUI config 5 | gradle-app.setting 6 | 7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 8 | !gradle-wrapper.jar 9 | 10 | # Cache of project 11 | .gradletasknamecache 12 | 13 | # IDEA 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | out/ 19 | 20 | # macOS 21 | .DS_Store 22 | 23 | # TeamCity DSL 24 | target/ 25 | 26 | -------------------------------------------------------------------------------- /.teamcity/Project.kt: -------------------------------------------------------------------------------- 1 | import jetbrains.buildServer.configs.kotlin.AbsoluteId 2 | import jetbrains.buildServer.configs.kotlin.BuildType 3 | import jetbrains.buildServer.configs.kotlin.CheckoutMode 4 | import jetbrains.buildServer.configs.kotlin.Project 5 | import jetbrains.buildServer.configs.kotlin.buildSteps.gradle 6 | import jetbrains.buildServer.configs.kotlin.triggers.vcs 7 | 8 | 9 | object Project : Project({ 10 | buildType(Verify) 11 | buildType(Publish) 12 | params { 13 | param("env.JAVA_HOME", "%linux.java17.openjdk.64bit%") 14 | param("env.GRADLE_CACHE_REMOTE_URL", "%gradle.cache.remote.url%") 15 | param("env.GRADLE_CACHE_REMOTE_USERNAME", "%gradle.cache.remote.username%") 16 | password("env.GRADLE_CACHE_REMOTE_PASSWORD", "%gradle.cache.remote.password%") 17 | password("env.DEVELOCITY_ACCESS_KEY", "%ge.gradle.org.access.key%") 18 | } 19 | }) 20 | 21 | object Verify : BuildType({ 22 | id = AbsoluteId("Build_Tool_Services_Exemplar_Verify") 23 | uuid = "Build_Tool_Services_Exemplar_Verify" 24 | name = "Verify Exemplar" 25 | description = "Verify integrity of Exemplar libraries" 26 | 27 | vcs { 28 | root(AbsoluteId("Exemplar_Master")) 29 | checkoutMode = CheckoutMode.ON_AGENT 30 | cleanCheckout = true 31 | } 32 | 33 | requirements { 34 | contains("teamcity.agent.jvm.os.name", "Linux") 35 | } 36 | 37 | triggers { 38 | vcs { 39 | branchFilter = """ 40 | +:refs/heads/* 41 | """.trimIndent() 42 | } 43 | } 44 | 45 | steps { 46 | gradle { 47 | useGradleWrapper = true 48 | tasks = "check" 49 | gradleParams = "-Dgradle.cache.remote.push=%env.BUILD_CACHE_PUSH%" 50 | buildFile = "build.gradle.kts" 51 | } 52 | } 53 | }) 54 | 55 | object Publish : BuildType({ 56 | id = AbsoluteId("Build_Tool_Services_Exemplar_Publish") 57 | uuid = "Build_Tool_Services_Exemplar_Publish" 58 | name = "Publish Exemplar" 59 | description = "Publish Exemplar libraries to Maven Central staging repository" 60 | 61 | vcs { 62 | root(AbsoluteId("Exemplar_Master")) 63 | checkoutMode = CheckoutMode.ON_AGENT 64 | cleanCheckout = true 65 | } 66 | 67 | requirements { 68 | contains("teamcity.agent.jvm.os.name", "Linux") 69 | } 70 | 71 | steps { 72 | gradle { 73 | useGradleWrapper = true 74 | tasks = "clean publishMavenJavaPublicationToSonatypeRepository" 75 | gradleParams = "-Dgradle.publish.skip.namespace.check=true" 76 | buildFile = "build.gradle.kts" 77 | } 78 | } 79 | params { 80 | param("env.MAVEN_CENTRAL_STAGING_REPO_USER", "%mavenCentralStagingRepoUser%") 81 | password("env.MAVEN_CENTRAL_STAGING_REPO_PASSWORD", "%mavenCentralStagingRepoPassword%") 82 | } 83 | }) 84 | -------------------------------------------------------------------------------- /.teamcity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | Exemplar TeamCity Config DSL 5 | org.gradle.exemplar 6 | exemplar-dsl 7 | 1.0-SNAPSHOT 8 | 9 | 10 | org.jetbrains.teamcity 11 | configs-dsl-kotlin-parent 12 | 1.0-SNAPSHOT 13 | 14 | 15 | 16 | 17 | jetbrains-all 18 | https://download.jetbrains.com/teamcity-repository 19 | 20 | true 21 | 22 | 23 | 24 | teamcity-server 25 | https://builds.gradle.org/app/dsl-plugins-repository 26 | 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | JetBrains 35 | https://download.jetbrains.com/teamcity-repository 36 | 37 | 38 | 39 | 40 | . 41 | 42 | 43 | kotlin-maven-plugin 44 | org.jetbrains.kotlin 45 | ${kotlin.version} 46 | 47 | 48 | 49 | 50 | compile 51 | process-sources 52 | 53 | compile 54 | 55 | 56 | 57 | test-compile 58 | process-test-sources 59 | 60 | test-compile 61 | 62 | 63 | 64 | 65 | 66 | org.jetbrains.teamcity 67 | teamcity-configs-maven-plugin 68 | ${teamcity.dsl.version} 69 | 70 | kotlin 71 | target/generated-configs 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.jetbrains.teamcity 80 | configs-dsl-kotlin-latest 81 | ${teamcity.dsl.version} 82 | compile 83 | 84 | 85 | org.jetbrains.teamcity 86 | configs-dsl-kotlin-plugins-latest 87 | 1.0-SNAPSHOT 88 | pom 89 | compile 90 | 91 | 92 | org.jetbrains.kotlin 93 | kotlin-stdlib-jdk8 94 | ${kotlin.version} 95 | compile 96 | 97 | 98 | org.jetbrains.kotlin 99 | kotlin-script-runtime 100 | ${kotlin.version} 101 | compile 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /.teamcity/settings.kts: -------------------------------------------------------------------------------- 1 | import jetbrains.buildServer.configs.kotlin.project 2 | import jetbrains.buildServer.configs.kotlin.version 3 | 4 | version = "2025.03" 5 | project(Project) 6 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.github.gradle-nexus.publish-plugin") version "2.0.0" 3 | } 4 | 5 | group = "org.gradle.exemplar" 6 | version = "1.0.3" 7 | 8 | nexusPublishing { 9 | repositories.apply { 10 | sonatype { 11 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 12 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 13 | username.set(System.getenv("MAVEN_CENTRAL_STAGING_REPO_USER")) 14 | password.set(System.getenv("MAVEN_CENTRAL_STAGING_REPO_PASSWORD")) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/exemplar.java-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | groovy 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | 10 | withJavadocJar() 11 | withSourcesJar() 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | tasks.withType().configureEach { 19 | (options as StandardJavadocDocletOptions).apply { 20 | addStringOption("Xdoclint:none", "-quiet") 21 | } 22 | } 23 | 24 | tasks.withType().configureEach { 25 | useJUnitPlatform() 26 | } 27 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/exemplar.publishing-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | signing 4 | } 5 | 6 | group = rootProject.group 7 | version = rootProject.version 8 | 9 | publishing { 10 | publications.create("mavenJava") { 11 | artifactId = project.name 12 | from(components["java"]) 13 | 14 | pom { 15 | name.set("Exemplar ${project.name}") 16 | description.set("Given a collection of sample projects, this library allows you to verify the samples' output.") 17 | url.set("https://github.com/gradle/exemplar") 18 | licenses { 19 | license { 20 | name.set("Apache-2.0") 21 | url.set("https://www.apache.org/licenses/LICENSE-2.0") 22 | } 23 | } 24 | developers { 25 | developer { 26 | name.set("The Gradle team") 27 | organization.set("Gradle Inc.") 28 | organizationUrl.set("https://gradle.com") 29 | } 30 | } 31 | scm { 32 | connection.set("scm:git:git://github.com/gradle/exemplar.git") 33 | developerConnection.set("scm:git:ssh://git@github.com:gradle/exemplar.git") 34 | url.set("https://github.com/gradle/exemplar") 35 | } 36 | } 37 | } 38 | } 39 | 40 | signing { 41 | sign(publishing.publications["mavenJava"]) 42 | useInMemoryPgpKeys(System.getenv("PGP_SIGNING_KEY"), System.getenv("PGP_SIGNING_KEY_PASSPHRASE")) 43 | } 44 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /docs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("exemplar.java-conventions") 3 | } 4 | 5 | dependencies { 6 | implementation(project(":samples-check")) 7 | } 8 | 9 | tasks.test { 10 | useJUnit() 11 | inputs.file("README.adoc").withPathSensitivity(PathSensitivity.RELATIVE) 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/test/java/org/gradle/exemplar/ReadmeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar; 17 | 18 | import org.gradle.exemplar.test.runner.GradleEmbeddedSamplesRunner; 19 | import org.gradle.exemplar.test.runner.SamplesRoot; 20 | import org.junit.runner.RunWith; 21 | 22 | @RunWith(GradleEmbeddedSamplesRunner.class) 23 | @SamplesRoot(".") 24 | public class ReadmeTest { 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Enforce kotlin codestyle https://kotlinlang.org/docs/code-style-migration-guide.html#migration-to-a-new-code-style 2 | kotlin.code.style=official 3 | kotlin.jvm.target.validation.mode = IGNORE 4 | 5 | org.gradle.caching=true 6 | org.gradle.configureondemand=true 7 | org.gradle.java.installations.auto-detect=false 8 | org.gradle.java.installations.auto-download=false 9 | org.gradle.kotlin.dsl.allWarningsAsErrors=true 10 | org.gradle.parallel=true 11 | systemProp.org.gradle.groovy.compilation.avoidance=true 12 | systemProp.org.gradle.kotlin.dsl.caching.buildcache=true 13 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | spock = "2.0-groovy-3.0" 3 | junit-vintage = "5.12.2" 4 | junit-launcher = "1.12.2" 5 | 6 | [libraries] 7 | asciidoctorj = "org.asciidoctor:asciidoctorj:1.5.8.1" 8 | commons-io = "commons-io:commons-io:2.18.0" 9 | commons-lang3 = "org.apache.commons:commons-lang3:3.17.0" 10 | groovy = "org.codehaus.groovy:groovy:3.0.8" 11 | junit4 = "junit:junit:4.13.2" 12 | junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit-vintage" } 13 | junit-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-launcher" } 14 | jsr305 = "com.google.code.findbugs:jsr305:3.0.2" 15 | objenesis = "org.objenesis:objenesis:3.2" 16 | spock-core = { module="org.spockframework:spock-core", version.ref="spock" } 17 | spock-junit4 = { module="org.spockframework:spock-junit4", version.ref="spock" } 18 | typesafe-config = "com.typesafe:config:1.4.3" 19 | 20 | [bundles] 21 | spock = ["spock-core", "spock-junit4"] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/exemplar/02b6c53c344ca792be380c92eb20682a89eb288a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /samples-check/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("exemplar.java-conventions") 3 | id("exemplar.publishing-conventions") 4 | } 5 | 6 | dependencies { 7 | api(project(":samples-discovery")) 8 | api(libs.junit4) 9 | compileOnly(libs.jsr305) 10 | implementation(libs.commons.io) 11 | implementation(libs.commons.lang3) 12 | implementation(gradleTestKit()) 13 | testImplementation(libs.groovy) 14 | testImplementation(libs.objenesis) 15 | testImplementation(libs.bundles.spock) 16 | testRuntimeOnly(libs.junit.vintage) 17 | testRuntimeOnly(libs.junit.launcher) 18 | } 19 | 20 | tasks.test { 21 | inputs.dir(layout.projectDirectory.dir("src/test/samples")) 22 | .withPropertyName("samplesDir") 23 | .withPathSensitivity(PathSensitivity.RELATIVE) 24 | 25 | useJUnitPlatform { 26 | excludeTags.add("org.gradle.exemplar.test.runner.CoveredByTests") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/executor/CliCommandExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.executor; 17 | 18 | import java.io.File; 19 | import java.io.OutputStream; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | public class CliCommandExecutor extends CommandExecutor { 24 | public CliCommandExecutor(File directory) { 25 | super(directory); 26 | } 27 | 28 | @Override 29 | public int run(final String executable, final List args, final List flags, final OutputStream output) { 30 | List commandLine = new ArrayList<>(); 31 | commandLine.add(executable); 32 | commandLine.addAll(flags); 33 | commandLine.addAll(args); 34 | 35 | try { 36 | run(new ProcessBuilder(commandLine), output); 37 | } catch (Exception e) { 38 | // TODO: get exit code 39 | return 1; 40 | } 41 | return 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/executor/CommandExecutionResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.executor; 17 | 18 | import org.gradle.exemplar.model.Command; 19 | 20 | public class CommandExecutionResult { 21 | private final String output; 22 | private final int exitCode; 23 | private final Command command; 24 | private final ExecutionMetadata executionMetadata; 25 | 26 | public CommandExecutionResult(final Command command, final int exitCode, final String output, final ExecutionMetadata executionMetadata) { 27 | this.command = command; 28 | this.exitCode = exitCode; 29 | this.output = output; 30 | this.executionMetadata = executionMetadata; 31 | } 32 | 33 | public Command getCommand() { 34 | return command; 35 | } 36 | 37 | public String getOutput() { 38 | return output; 39 | } 40 | 41 | public int getExitCode() { 42 | return exitCode; 43 | } 44 | 45 | public ExecutionMetadata getExecutionMetadata() { 46 | return executionMetadata; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/executor/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.executor; 17 | 18 | import org.apache.commons.lang3.StringUtils; 19 | import org.gradle.exemplar.model.Command; 20 | 21 | import javax.annotation.Nullable; 22 | import java.io.*; 23 | import java.util.List; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | public abstract class CommandExecutor { 29 | private final File directory; 30 | 31 | public CommandExecutor() { 32 | this.directory = null; 33 | } 34 | 35 | protected CommandExecutor(final File directory) { 36 | this.directory = directory; 37 | } 38 | 39 | protected abstract int run(final String executable, final List args, final List flags, final OutputStream output); 40 | 41 | public void run(ProcessBuilder processBuilder, final OutputStream outputStream) { 42 | run(processBuilder, outputStream, null, null).waitForSuccess(); 43 | } 44 | 45 | protected CommandExecutor.RunHandle run(final ProcessBuilder processBuilder, final OutputStream outputStream, @Nullable final OutputStream errorStream, @Nullable final InputStream inputStream) { 46 | if (directory != null) { 47 | processBuilder.directory(directory); 48 | } 49 | final String command = processBuilder.command().get(0); 50 | try { 51 | if (errorStream == null) { 52 | processBuilder.redirectErrorStream(true); 53 | } 54 | final Process process = processBuilder.start(); 55 | ExecutorService executor = Executors.newFixedThreadPool(3); 56 | executor.execute(new Runnable() { 57 | @Override 58 | public void run() { 59 | byte[] buffer = new byte[4096]; 60 | while (true) { 61 | if (readStream(process.getInputStream(), outputStream, command, buffer)) break; 62 | } 63 | } 64 | }); 65 | 66 | if (errorStream != null) { 67 | executor.execute(new Runnable() { 68 | @Override 69 | public void run() { 70 | byte[] buffer = new byte[4096]; 71 | while (true) { 72 | if (readStream(process.getErrorStream(), errorStream, command, buffer)) break; 73 | } 74 | } 75 | }); 76 | } 77 | if (inputStream != null) { 78 | executor.execute(new Runnable() { 79 | @Override 80 | public void run() { 81 | byte[] buffer = new byte[4096]; 82 | OutputStream output = process.getOutputStream(); 83 | while (true) { 84 | try { 85 | int read = inputStream.read(buffer); 86 | output.write(buffer); 87 | if (read == -1) { 88 | output.flush(); 89 | output.close(); 90 | break; 91 | } 92 | } catch (IOException e) { 93 | throw new RuntimeException("Could not write input", e); 94 | } 95 | } 96 | } 97 | }); 98 | } 99 | return new CommandExecutor.RunHandle(processBuilder, process, executor); 100 | } catch (IOException e) { 101 | throw new RuntimeException(commandErrorMessage(processBuilder), e); 102 | } 103 | } 104 | 105 | private boolean readStream(InputStream inputStream, OutputStream outputStream, String command, byte[] buffer) { 106 | int nread; 107 | try { 108 | nread = inputStream.read(buffer); 109 | } catch (IOException e) { 110 | throw new RuntimeException("Could not read input from child process for command '" + command + "'", e); 111 | } 112 | if (nread < 0) { 113 | return true; 114 | } 115 | try { 116 | outputStream.write(buffer, 0, nread); 117 | } catch (IOException e) { 118 | throw new RuntimeException("Could not write output from child process for command '" + command + "'", e); 119 | } 120 | return false; 121 | } 122 | 123 | private String commandErrorMessage(ProcessBuilder processBuilder) { 124 | return "Could not run command " + StringUtils.join(processBuilder.command(), " "); 125 | } 126 | 127 | public class RunHandle { 128 | private final ProcessBuilder processBuilder; 129 | private final Process process; 130 | 131 | private final ExecutorService executor; 132 | 133 | RunHandle(ProcessBuilder processBuilder, Process process, ExecutorService executor) { 134 | this.processBuilder = processBuilder; 135 | this.process = process; 136 | this.executor = executor; 137 | } 138 | 139 | public void waitForSuccess() { 140 | int result; 141 | try { 142 | result = process.waitFor(); 143 | } catch (Exception e) { 144 | throw new RuntimeException(commandErrorMessage(processBuilder), e); 145 | } finally { 146 | shutdownExecutor(); 147 | } 148 | if (result != 0) { 149 | throw new RuntimeException(commandErrorMessage(processBuilder) + ". Exited with result " + result); 150 | } 151 | } 152 | 153 | private void shutdownExecutor() { 154 | try { 155 | executor.shutdown(); 156 | executor.awaitTermination(10, TimeUnit.SECONDS); 157 | } catch (InterruptedException ignored) { 158 | } 159 | } 160 | } 161 | 162 | public CommandExecutionResult execute(final Command command, final ExecutionMetadata executionMetadata) { 163 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 164 | final int exitCode = run(command.getExecutable(), command.getArgs(), command.getFlags(), outputStream); 165 | 166 | return new CommandExecutionResult(command, exitCode, outputStream.toString(), executionMetadata); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/executor/ExecutionMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.executor; 17 | 18 | import java.io.File; 19 | import java.util.Map; 20 | 21 | public class ExecutionMetadata { 22 | private File tempSampleProjectDir; 23 | private Map systemProperties; 24 | 25 | public ExecutionMetadata(File tempSampleProjectDir, Map systemProperties) { 26 | this.tempSampleProjectDir = tempSampleProjectDir; 27 | this.systemProperties = systemProperties; 28 | } 29 | 30 | public File getTempSampleProjectDir() { 31 | return tempSampleProjectDir; 32 | } 33 | 34 | public Map getSystemProperties() { 35 | return systemProperties; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/executor/GradleRunnerCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.executor; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.gradle.testkit.runner.BuildResult; 5 | import org.gradle.testkit.runner.GradleRunner; 6 | 7 | import javax.annotation.Nullable; 8 | import java.io.File; 9 | import java.io.OutputStream; 10 | import java.io.OutputStreamWriter; 11 | import java.io.Writer; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class GradleRunnerCommandExecutor extends CommandExecutor { 16 | private final File workingDir; 17 | private final File customGradleInstallation; 18 | private final boolean expectFailure; 19 | 20 | public GradleRunnerCommandExecutor(File workingDir, @Nullable File customGradleInstallation, boolean expectFailure) { 21 | this.workingDir = workingDir; 22 | this.customGradleInstallation = customGradleInstallation; 23 | this.expectFailure = expectFailure; 24 | } 25 | 26 | @Override 27 | protected int run(String executable, List args, List flags, OutputStream output) { 28 | List allArguments = new ArrayList<>(args); 29 | allArguments.addAll(flags); 30 | 31 | GradleRunner gradleRunner = GradleRunner.create() 32 | .withProjectDir(workingDir) 33 | .withArguments(allArguments) 34 | .forwardOutput(); 35 | 36 | if (customGradleInstallation != null) { 37 | gradleRunner.withGradleInstallation(customGradleInstallation); 38 | } 39 | 40 | Writer mergedOutput = new OutputStreamWriter(output); 41 | try { 42 | BuildResult buildResult; 43 | if (expectFailure) { 44 | buildResult = gradleRunner.buildAndFail(); 45 | } else { 46 | buildResult = gradleRunner.build(); 47 | } 48 | mergedOutput.write(buildResult.getOutput()); 49 | mergedOutput.close(); 50 | return expectFailure ? 1 : 0; 51 | } catch (Exception e) { 52 | throw new RuntimeException("Could not execute " + executable, e); 53 | } finally { 54 | IOUtils.closeQuietly(mergedOutput); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/AsciidoctorAnnotationOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer; 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata; 4 | 5 | import java.util.Arrays; 6 | import java.util.regex.Pattern; 7 | import java.util.stream.Collectors; 8 | 9 | public class AsciidoctorAnnotationOutputNormalizer implements OutputNormalizer { 10 | private static final Pattern ASCIIDOCTOR_ANNOTATION_PATTERN = Pattern.compile("\\s+// <\\d+>$"); 11 | 12 | @Override 13 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 14 | return Arrays.stream(commandOutput.split("\\r?\\n", -1)) 15 | .map(AsciidoctorAnnotationOutputNormalizer::stripAsciidoctorAnnotation) 16 | .collect(Collectors.joining("\n")); 17 | } 18 | 19 | private static String stripAsciidoctorAnnotation(String line) { 20 | if (ASCIIDOCTOR_ANNOTATION_PATTERN.matcher(line).find()) { 21 | return ASCIIDOCTOR_ANNOTATION_PATTERN.matcher(line).replaceFirst(""); 22 | } 23 | return line; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/FileSeparatorOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.normalizer; 17 | 18 | import org.gradle.exemplar.executor.ExecutionMetadata; 19 | 20 | import java.io.File; 21 | import java.util.regex.Pattern; 22 | 23 | public class FileSeparatorOutputNormalizer implements OutputNormalizer { 24 | @Override 25 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 26 | return normalize(commandOutput, executionMetadata, File.separatorChar); 27 | } 28 | 29 | protected String normalize(String commandOutput, ExecutionMetadata executionMetadata, char separatorChar) { 30 | return commandOutput.replaceAll(Pattern.quote(String.valueOf(separatorChar)) + "(?=\\w)", "/"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/GradleOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.normalizer; 17 | 18 | import org.apache.commons.lang3.StringUtils; 19 | import org.gradle.exemplar.executor.ExecutionMetadata; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.regex.Pattern; 25 | 26 | public class GradleOutputNormalizer implements OutputNormalizer { 27 | private static final Pattern STACK_TRACE_ELEMENT = Pattern.compile("\\s+(at\\s+)?([\\w.$_]+/)?[\\w.$_]+\\.[\\w$_ =+\'-<>]+\\(.+?\\)(\\x1B\\[0K)?"); 28 | private static final Pattern BUILD_RESULT_PATTERN = Pattern.compile("BUILD (SUCCESSFUL|FAILED)( in \\d+(ms|s|m|h)( \\d+(ms|s|m|h))*)?"); 29 | private static final Pattern DOCUMENTATION_URL_PATTERN = Pattern.compile("https://docs.gradle.org/((\\d+.\\d+(.\\d+)?(-\\d+\\+0000)?)|current|nightly)/"); 30 | private static final Pattern BUILD_SCAN_URL_PATTERN = Pattern.compile("https://gradle.com/s/([a-z0-9]+)"); 31 | 32 | public static final String DOWNLOAD_MESSAGE_PREFIX = "Download "; 33 | public static final String GENERATING_JAR_PREFIX = "Generating JAR file 'gradle-api-"; 34 | 35 | public static final String DOWNLOADING_WRAPPER_MESSAGE_PREFIX = "Downloading https://services.gradle.org/distributions"; 36 | public static final String DOWNLOADING_WRAPPER_PROGRESS_PREFIX = ".........10%"; 37 | 38 | public static final String INCUBATING_FEATURE_SUFFIX = "incubating feature."; 39 | 40 | // Duplicating here to avoid use of Gradle's internal API 41 | public static final String STARTING_A_GRADLE_DAEMON_MESSAGE = "Starting a Gradle Daemon"; 42 | public static final String DAEMON_WILL_BE_STOPPED_MESSAGE = "Daemon will be stopped at the end of the build"; 43 | public static final String EXPIRING_DAEMON_MESSAGE = "Expiring Daemon because JVM Tenured space is exhausted"; 44 | public static final String DEPRECATED_GRADLE_FEATURES_MESSAGE = "Deprecated Gradle features were used in this build, making it incompatible with Gradle"; 45 | public static final String JAVA_7_DEPRECATION_MESSAGE = "Support for running Gradle using Java 7 has been deprecated and is scheduled to be removed in Gradle 5.0."; 46 | 47 | @Override 48 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 49 | //commandOutput = commandOutput.replaceAll(executionMetadata.getTempSampleProjectDir().getAbsolutePath(), NORMALIZED_SAMPLES_PATH); 50 | List result = new ArrayList<>(); 51 | final List lines = Arrays.asList(commandOutput.split("\\r?\\n", -1)); 52 | int i = 0; 53 | while (i < lines.size()) { 54 | String line = lines.get(i); 55 | if (line.startsWith(GENERATING_JAR_PREFIX)) { 56 | i++; 57 | } else if (line.startsWith(DOWNLOAD_MESSAGE_PREFIX)) { 58 | i++; 59 | } else if (line.startsWith(DOWNLOADING_WRAPPER_MESSAGE_PREFIX)) { 60 | // Remove the "Dowloading https://..." message for wrapper 61 | i++; 62 | } else if (line.startsWith(DOWNLOADING_WRAPPER_PROGRESS_PREFIX)) { 63 | // Remove the ".........10%.........20%" message 64 | i++; 65 | } else if (line.endsWith(INCUBATING_FEATURE_SUFFIX)) { 66 | // Remove the "...incubating feature." message 67 | i++; 68 | } else if (line.contains(STARTING_A_GRADLE_DAEMON_MESSAGE)) { 69 | // Remove the "daemon starting" message 70 | i++; 71 | } else if (line.contains(DAEMON_WILL_BE_STOPPED_MESSAGE)) { 72 | // Remove the "Daemon will be shut down" message 73 | i++; 74 | } else if (line.contains(EXPIRING_DAEMON_MESSAGE)) { 75 | // Remove the "Expiring Daemon" message 76 | i++; 77 | } else if (line.contains(DEPRECATED_GRADLE_FEATURES_MESSAGE)) { 78 | // Remove the "Deprecated Gradle features..." message and "See https://docs.gradle.org..." 79 | i+=2; 80 | } else if (line.contains(JAVA_7_DEPRECATION_MESSAGE)) { 81 | // Remove the Java 7 deprecation warning. This should be removed after 5.0 82 | i++; 83 | while (i < lines.size() && STACK_TRACE_ELEMENT.matcher(lines.get(i)).matches()) { 84 | i++; 85 | } 86 | } else if (BUILD_RESULT_PATTERN.matcher(line).matches()) { 87 | result.add(BUILD_RESULT_PATTERN.matcher(line).replaceFirst("BUILD $1 in 0s")); 88 | i++; 89 | } else { 90 | if (DOCUMENTATION_URL_PATTERN.matcher(line).find()) { 91 | line = DOCUMENTATION_URL_PATTERN.matcher(line).replaceFirst("https://docs.gradle.org/0.0.0/"); 92 | } else if (BUILD_SCAN_URL_PATTERN.matcher(line).find()) { 93 | line = BUILD_SCAN_URL_PATTERN.matcher(line).replaceFirst("https://gradle.com/s/feeedfooc00de"); 94 | } 95 | result.add(line); 96 | i++; 97 | } 98 | } 99 | 100 | return StringUtils.join(result, "\n"); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/JavaObjectSerializationOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.normalizer; 17 | 18 | import org.gradle.exemplar.executor.ExecutionMetadata; 19 | 20 | public class JavaObjectSerializationOutputNormalizer implements OutputNormalizer { 21 | @Override 22 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 23 | return commandOutput.replaceAll("(\\w+(\\.\\w+)*)@\\p{XDigit}+", "$1@12345"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/LeadingNewLineOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer; 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class LeadingNewLineOutputNormalizer implements OutputNormalizer { 11 | @Override 12 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 13 | if (commandOutput.isEmpty()) { 14 | return commandOutput; 15 | } 16 | List lines = new ArrayList<>(Arrays.asList(commandOutput.split("\\r?\\n", -1))); 17 | 18 | while (lines.get(0).isEmpty()) { 19 | lines.remove(0); 20 | } 21 | 22 | return lines.stream().collect(Collectors.joining("\n")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/OutputNormalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.normalizer; 17 | 18 | import org.gradle.exemplar.executor.ExecutionMetadata; 19 | 20 | public interface OutputNormalizer { 21 | /** 22 | * Remove and update output that is unrelated the the sample output. 23 | * 24 | * @param commandOutput raw command output 25 | * @param executionMetadata environment and execution information 26 | * @return normalized output as a String 27 | */ 28 | String normalize(final String commandOutput, final ExecutionMetadata executionMetadata); 29 | } 30 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/StripTrailingOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer; 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | 8 | public class StripTrailingOutputNormalizer implements OutputNormalizer { 9 | @Override 10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 11 | return Arrays.stream(commandOutput.split("\\r?\\n", -1)).map(StripTrailingOutputNormalizer::stripTrailing).collect(Collectors.joining("\n")); 12 | } 13 | 14 | private static String stripTrailing(String self) { 15 | int len = self.length(); 16 | int st = 0; 17 | char[] val = self.toCharArray(); /* avoid getfield opcode */ 18 | 19 | while ((st < len) && (Character.isSpaceChar(val[len - 1]))) { 20 | len--; 21 | } 22 | return ((len < self.length())) ? self.substring(0, len) : self; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/TrailingNewLineOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer; 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata; 4 | 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | 8 | public class TrailingNewLineOutputNormalizer implements OutputNormalizer { 9 | @Override 10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 11 | if (commandOutput.isEmpty()) { 12 | return commandOutput; 13 | } 14 | return Arrays.stream(commandOutput.split("\\r?\\n")).collect(Collectors.joining("\n")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/normalizer/WorkingDirectoryOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer; 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata; 4 | 5 | import java.io.IOException; 6 | import java.io.UncheckedIOException; 7 | 8 | public class WorkingDirectoryOutputNormalizer implements OutputNormalizer { 9 | @Override 10 | public String normalize(String commandOutput, ExecutionMetadata executionMetadata) { 11 | try { 12 | return commandOutput.replace(executionMetadata.getTempSampleProjectDir().getCanonicalPath(), "/working-directory"); 13 | } catch (IOException e) { 14 | throw new UncheckedIOException(e); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/rule/Sample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.exemplar.test.rule; 18 | 19 | import org.apache.commons.io.FileUtils; 20 | import org.junit.rules.TemporaryFolder; 21 | import org.junit.rules.TestRule; 22 | import org.junit.runner.Description; 23 | import org.junit.runners.model.Statement; 24 | 25 | import java.io.Closeable; 26 | import java.io.File; 27 | import java.io.IOException; 28 | 29 | import static org.junit.Assert.assertNotNull; 30 | 31 | /** 32 | * A JUnit rule which copies a sample into the test directory before the test executes. 33 | * 34 | *

Looks for a {@link UsesSample} annotation on the test method to determine which sample the 35 | * test requires. If not found, uses the default sample provided in the constructor. 36 | */ 37 | public class Sample implements TestRule { 38 | 39 | private final SourceSampleDirSupplier sourceSampleDirSupplier; 40 | private TargetBaseDirSupplier targetBaseDirSupplier; 41 | private String defaultSampleName; 42 | 43 | private String sampleName; 44 | private File targetDir; 45 | 46 | public static Sample from(final String sourceBaseDirPath) { 47 | return from(new SourceSampleDirSupplier() { 48 | @Override 49 | public File getDir(String sampleName) { 50 | return new File(sourceBaseDirPath, sampleName); 51 | } 52 | }); 53 | } 54 | 55 | public static Sample from(SourceSampleDirSupplier sourceSampleDirSupplier) { 56 | return new Sample(sourceSampleDirSupplier); 57 | } 58 | 59 | private Sample(SourceSampleDirSupplier sourceSampleDirSupplier) { 60 | this.sourceSampleDirSupplier = sourceSampleDirSupplier; 61 | } 62 | 63 | /** 64 | * Copy the samples into the supplied {@link TemporaryFolder}. 65 | * 66 | * @deprecated please use {@link #intoTemporaryFolder()} or {@link #intoTemporaryFolder(File)} 67 | */ 68 | @Deprecated 69 | public Sample into(final TemporaryFolder temporaryFolder) { 70 | return into(new TargetBaseDirSupplier() { 71 | @Override 72 | public File getDir() { 73 | try { 74 | return temporaryFolder.newFolder("samples"); 75 | } catch (IOException e) { 76 | throw new RuntimeException("Could not create samples target base dir", e); 77 | } 78 | } 79 | }); 80 | } 81 | 82 | /** 83 | * Copy the samples into a temporary folder that is attempted to be deleted afterwards. 84 | */ 85 | public Sample intoTemporaryFolder() { 86 | return intoTemporaryFolder(null); 87 | } 88 | 89 | /** 90 | * Copy the samples into a temporary folder that is attempted to be deleted afterwards. 91 | * 92 | * @param parentFolder The parent folder of the created temporary folder 93 | */ 94 | public Sample intoTemporaryFolder(File parentFolder) { 95 | return into(new ManagedTemporaryFolder(parentFolder)); 96 | } 97 | 98 | /** 99 | * Copy the samples into a folder returned by the supplied {@link TargetBaseDirSupplier}. 100 | * 101 | * @see TargetBaseDirSupplier 102 | */ 103 | public Sample into(TargetBaseDirSupplier targetBaseDirSupplier) { 104 | this.targetBaseDirSupplier = targetBaseDirSupplier; 105 | return this; 106 | } 107 | 108 | public Sample withDefaultSample(String name) { 109 | this.defaultSampleName = name; 110 | return this; 111 | } 112 | 113 | public interface SourceSampleDirSupplier { 114 | File getDir(String sampleName); 115 | } 116 | 117 | /** 118 | * Supplier for the base directory into which samples are copied. 119 | * 120 | * May optionally implement {@link Closeable} in which case it will be called after test execution to clean up. 121 | */ 122 | public interface TargetBaseDirSupplier { 123 | File getDir(); 124 | } 125 | 126 | @Override 127 | public Statement apply(final Statement base, Description description) { 128 | if (targetBaseDirSupplier == null) { 129 | intoTemporaryFolder(); 130 | } 131 | sampleName = getSampleName(description); 132 | return new Statement() { 133 | @Override 134 | public void evaluate() throws Throwable { 135 | assertNotNull("No sample selected. Please use @UsesSample or withDefaultSample()", sampleName); 136 | try { 137 | File srcDir = sourceSampleDirSupplier.getDir(sampleName); 138 | FileUtils.copyDirectory(srcDir, getDir()); 139 | base.evaluate(); 140 | } finally { 141 | if (targetBaseDirSupplier instanceof Closeable) { 142 | ((Closeable) targetBaseDirSupplier).close(); 143 | } 144 | } 145 | } 146 | }; 147 | } 148 | 149 | private String getSampleName(Description description) { 150 | UsesSample annotation = description.getAnnotation(UsesSample.class); 151 | return annotation != null 152 | ? annotation.value() 153 | : defaultSampleName; 154 | } 155 | 156 | public File getDir() { 157 | if (targetDir == null) { 158 | targetDir = computeSampleDir(); 159 | } 160 | return targetDir; 161 | } 162 | 163 | private File computeSampleDir() { 164 | String subDirName = getSampleTargetDirName(); 165 | return new File(targetBaseDirSupplier.getDir(), subDirName); 166 | } 167 | 168 | private String getSampleTargetDirName() { 169 | if (sampleName == null) { 170 | throw new IllegalStateException("This rule hasn't been applied, yet."); 171 | } 172 | return sampleName; 173 | } 174 | 175 | private static class ManagedTemporaryFolder implements TargetBaseDirSupplier, Closeable { 176 | private final TemporaryFolder temporaryFolder; 177 | 178 | public ManagedTemporaryFolder(File parentFolder) { 179 | this.temporaryFolder = new TemporaryFolder(parentFolder); 180 | } 181 | 182 | @Override 183 | public File getDir() { 184 | try { 185 | temporaryFolder.create(); 186 | return temporaryFolder.getRoot(); 187 | } catch (IOException e) { 188 | throw new RuntimeException("Could not create samples target base dir", e); 189 | } 190 | } 191 | 192 | @Override 193 | public void close() { 194 | temporaryFolder.delete(); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/rule/UsesSample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.rule; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Inherited; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Specifies the relative path of the sample to use for a test. 27 | * 28 | * @see Sample 29 | */ 30 | @Documented 31 | @Inherited 32 | @Retention(RetentionPolicy.RUNTIME) 33 | @Target(ElementType.METHOD) 34 | public @interface UsesSample { 35 | String value(); 36 | } 37 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/EmbeddedSamplesRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.loader.SamplesDiscovery; 19 | import org.gradle.exemplar.model.Sample; 20 | import org.junit.runners.model.InitializationError; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.List; 25 | 26 | public class EmbeddedSamplesRunner extends SamplesRunner { 27 | 28 | /** 29 | * Constructs a new {@code ParentRunner} that will run {@code @TestClass} 30 | * 31 | * @param testClass reference to test class being run 32 | */ 33 | public EmbeddedSamplesRunner(Class testClass) throws InitializationError { 34 | super(testClass); 35 | } 36 | 37 | @Override 38 | protected List getChildren() { 39 | File samplesRootDir = getSamplesRootDir(); 40 | try { 41 | return SamplesDiscovery.embeddedSamples(samplesRootDir); 42 | } catch (IOException e) { 43 | throw new RuntimeException("Could not extract samples from " + samplesRootDir, e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/GradleEmbeddedSamplesRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.loader.SamplesDiscovery; 19 | import org.gradle.exemplar.model.Sample; 20 | import org.junit.runners.model.InitializationError; 21 | 22 | import java.io.IOException; 23 | import java.util.List; 24 | 25 | /** 26 | * A custom implementation of {@link SamplesRunner} that uses the Gradle Tooling API to execute sample builds. 27 | */ 28 | public class GradleEmbeddedSamplesRunner extends GradleSamplesRunner { 29 | public GradleEmbeddedSamplesRunner(Class testClass) throws InitializationError { 30 | super(testClass); 31 | } 32 | 33 | @Override 34 | protected List getChildren() { 35 | try { 36 | return SamplesDiscovery.embeddedSamples(getSamplesRootDir()); 37 | } catch (IOException e) { 38 | throw new RuntimeException("Could not extract embedded samples", e); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/GradleSamplesRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.api.JavaVersion; 19 | import org.gradle.exemplar.executor.CliCommandExecutor; 20 | import org.gradle.exemplar.executor.CommandExecutor; 21 | import org.gradle.exemplar.executor.ExecutionMetadata; 22 | import org.gradle.exemplar.executor.GradleRunnerCommandExecutor; 23 | import org.gradle.exemplar.model.Command; 24 | import org.gradle.exemplar.model.Sample; 25 | import org.junit.Rule; 26 | import org.junit.rules.TemporaryFolder; 27 | import org.junit.runners.model.InitializationError; 28 | 29 | import javax.annotation.Nullable; 30 | import java.io.File; 31 | 32 | /** 33 | * A custom implementation of {@link SamplesRunner} that uses the Gradle Tooling API to execute sample builds. 34 | */ 35 | public class GradleSamplesRunner extends SamplesRunner { 36 | private static final String GRADLE_EXECUTABLE = "gradle"; 37 | @Rule 38 | public TemporaryFolder tempGradleUserHomeDir = new TemporaryFolder(); 39 | private File customGradleInstallation = null; 40 | 41 | public GradleSamplesRunner(Class testClass) throws InitializationError { 42 | super(testClass); 43 | } 44 | 45 | /** 46 | * Gradle samples tests are ignored on Java 7 and below. 47 | */ 48 | @Override 49 | protected boolean isIgnored(Sample child) { 50 | return !JavaVersion.current().isJava8Compatible(); 51 | } 52 | 53 | @Override 54 | protected CommandExecutor selectExecutor(ExecutionMetadata executionMetadata, File workingDir, Command command) { 55 | boolean expectFailure = command.isExpectFailure(); 56 | if (command.getExecutable().equals(GRADLE_EXECUTABLE)) { 57 | return new GradleRunnerCommandExecutor(workingDir, customGradleInstallation, expectFailure); 58 | } 59 | return new CliCommandExecutor(workingDir); 60 | } 61 | 62 | @Nullable 63 | @Override 64 | protected File getImplicitSamplesRootDir() { 65 | String gradleHomeDir = getCustomGradleInstallationFromSystemProperty(); 66 | if (System.getProperty("integTest.samplesdir") != null) { 67 | String samplesRootProperty = System.getProperty("integTest.samplesdir", gradleHomeDir + "/samples"); 68 | return new File(samplesRootProperty); 69 | } else if (customGradleInstallation != null) { 70 | return new File(customGradleInstallation, "samples"); 71 | } else { 72 | return null; 73 | } 74 | } 75 | 76 | @Nullable 77 | private String getCustomGradleInstallationFromSystemProperty() { 78 | // Allow Gradle installation and samples root dir to be set from a system property 79 | // This is to allow Gradle to test Gradle installations during integration testing 80 | final String gradleHomeDirProperty = System.getProperty("integTest.gradleHomeDir"); 81 | if (gradleHomeDirProperty != null) { 82 | File customGradleInstallationDir = new File(gradleHomeDirProperty); 83 | if (customGradleInstallationDir.exists()) { 84 | this.customGradleInstallation = customGradleInstallationDir; 85 | } else { 86 | throw new RuntimeException(String.format("Custom Gradle installation dir at %s was not found", gradleHomeDirProperty)); 87 | } 88 | } 89 | return gradleHomeDirProperty; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/SampleModifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.model.Sample; 19 | 20 | /** 21 | * Modifies a given {@link Sample} before it is processed. 22 | */ 23 | public interface SampleModifier { 24 | Sample modify(Sample sampleIn); 25 | } 26 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/SampleModifiers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import java.lang.annotation.*; 19 | 20 | /** 21 | * Specifies execution update classes to invoke before the execution. 22 | */ 23 | @Documented 24 | @Inherited 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface SampleModifiers { 28 | Class[] value(); 29 | } 30 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/SamplesOutputNormalizers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.test.normalizer.OutputNormalizer; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * Specifies output normalizer classes to invoke on a given subset of samples. 24 | * 25 | * @see SamplesRunner 26 | */ 27 | @Documented 28 | @Inherited 29 | @Target(ElementType.TYPE) 30 | @Retention(RetentionPolicy.RUNTIME) 31 | public @interface SamplesOutputNormalizers { 32 | Class[] value(); 33 | } 34 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/SamplesRoot.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import java.lang.annotation.*; 19 | 20 | /** 21 | * Specifies the directory to find samples, be they external or embedded. 22 | * 23 | * This directory is relative to project where Exemplar is invoked. 24 | * 25 | * For example, given this structure: 26 | * 27 | *

28 |  * monorepo/
29 |  * ├── build.gradle
30 |  * ├── subprojectBar/
31 |  * │   └── build.gradle
32 |  * │   └── src/
33 |  * │       ├── samples/
34 |  * │       │   └── bar.adoc
35 |  * │       └── test/
36 |  * │           └── java/
37 |  * │               └── DocsSampleTest.java
38 |  * └── subprojectFoo/
39 |  *     └── src/
40 |  * 
41 | * 42 | * ...DocsSampleTest should declare @AsciidocSourcesRoot("src/samples"). 43 | * 44 | * @see SamplesRunner 45 | */ 46 | @Documented 47 | @Inherited 48 | @Target(ElementType.TYPE) 49 | @Retention(RetentionPolicy.RUNTIME) 50 | public @interface SamplesRoot { 51 | String value(); 52 | } 53 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/runner/Transformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | /** 19 | *

A {@code Transformer} transforms objects of type.

20 | * 21 | *

Implementations are free to return new objects or mutate the incoming value.

22 | * 23 | * @param The type the value is transformed to. 24 | * @param The type of the value to be transformed. 25 | */ 26 | public interface Transformer { 27 | /** 28 | * Transforms the given object, and returns the transformed value. 29 | * 30 | * @param in The object to update. 31 | * @return The transformed object. 32 | */ 33 | OUT transform(IN in); 34 | } 35 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/verifier/AnyOrderLineSegmentedOutputVerifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.verifier; 17 | 18 | import org.apache.commons.lang3.StringUtils; 19 | import org.junit.Assert; 20 | 21 | import java.util.Arrays; 22 | import java.util.LinkedList; 23 | 24 | public class AnyOrderLineSegmentedOutputVerifier implements OutputVerifier { 25 | private static final String NEWLINE = System.getProperty("line.separator"); 26 | 27 | public void verify(final String expected, final String actual, final boolean allowAdditionalOutput) { 28 | // ArrayList does not support removal, and deletions for linked lists are O(1) 29 | LinkedList expectedLines = new LinkedList<>(Arrays.asList(expected.replaceAll("(\\r?\\n)+", "\n").split("\\r?\\n"))); 30 | LinkedList unmatchedLines = new LinkedList<>(Arrays.asList(actual.replaceAll("(\\r?\\n)+", "\n").split("\\r?\\n"))); 31 | 32 | for (String expectedLine : expectedLines) { 33 | String matchedLine = null; 34 | for (String unmatchedLine : unmatchedLines) { 35 | if (unmatchedLine.equals(expectedLine)) { 36 | matchedLine = unmatchedLine; 37 | } 38 | } 39 | if (matchedLine != null) { 40 | unmatchedLines.remove(matchedLine); 41 | } else { 42 | Assert.fail(String.format("Line missing from output.%n%s%n---%nActual output:%n%s%n---", expectedLine, actual)); 43 | } 44 | } 45 | 46 | if (!(allowAdditionalOutput || unmatchedLines.isEmpty())) { 47 | String unmatched = StringUtils.join(unmatchedLines, NEWLINE); 48 | Assert.fail(String.format("Extra lines in output.%n%s%n---%nActual output:%n%s%n---", unmatched, actual)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/verifier/OutputVerifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.verifier; 17 | 18 | public interface OutputVerifier { 19 | void verify(final String expected, final String actual, final boolean allowAdditionalOutput); 20 | } 21 | -------------------------------------------------------------------------------- /samples-check/src/main/java/org/gradle/exemplar/test/verifier/StrictOrderLineSegmentedOutputVerifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.verifier; 17 | 18 | import org.junit.Assert; 19 | 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | public class StrictOrderLineSegmentedOutputVerifier implements OutputVerifier { 24 | public void verify(final String expected, final String actual, final boolean allowAdditionalOutput) { 25 | List expectedLines = Arrays.asList(expected.split("\\r?\\n")); 26 | List actualLines = Arrays.asList(actual.split("\\r?\\n")); 27 | 28 | int expectedIndex = 0; 29 | int actualIndex = 0; 30 | if (allowAdditionalOutput) { 31 | actualIndex = findFirstMatchingLine(actualLines, expectedLines.get(expectedIndex)); 32 | } 33 | for (; actualIndex < actualLines.size() && expectedIndex < expectedLines.size(); actualIndex++, expectedIndex++) { 34 | final String expectedLine = expectedLines.get(expectedIndex); 35 | final String actualLine = actualLines.get(actualIndex); 36 | if (!expectedLine.equals(actualLine)) { 37 | if (expectedLine.contains(actualLine)) { 38 | Assert.fail(String.format("Missing text at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual)); 39 | } 40 | if (actualLine.contains(expectedLine)) { 41 | Assert.fail(String.format("Extra text at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual)); 42 | } 43 | Assert.fail(String.format("Unexpected value at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual)); 44 | } 45 | } 46 | 47 | if (actualIndex == actualLines.size() && expectedIndex < expectedLines.size()) { 48 | Assert.fail(String.format("Lines missing from actual result, starting at expected line %d.%nExpected: %s%nActual output:%n%s%n", expectedIndex, expectedLines.get(expectedIndex), actual)); 49 | } 50 | if (!allowAdditionalOutput && actualIndex < actualLines.size() && expectedIndex == expectedLines.size()) { 51 | Assert.fail(String.format("Extra lines in actual result, starting at line %d.%nActual: %s%nActual output:%n%s%n", actualIndex + 1, actualLines.get(actualIndex), actual)); 52 | } 53 | } 54 | 55 | private int findFirstMatchingLine(List actualLines, String expected) { 56 | int index = 0; 57 | for (; index < actualLines.size(); index++) { 58 | if (actualLines.get(index).equals(expected)) { 59 | return index; 60 | } 61 | } 62 | return actualLines.size(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /samples-check/src/test/docs/embedded-test.adoc: -------------------------------------------------------------------------------- 1 | = Embedding sample configuration 2 | 3 | This doc demonstrates how one can declare embedded commands instead of using `.sample.conf` files. 4 | 5 | == Embedded commands example 6 | 7 | Here we see sources that are `include`d, and inline commands and output. 8 | 9 | [.testable-sample,dir="src/test/samples/cli/quickstart"] 10 | .CLI quickstart sample 11 | ==== 12 | 13 | .sample.sh 14 | [source,bash] 15 | ---- 16 | include::src/test/samples/cli/quickstart/sample.sh[] 17 | ---- 18 | 19 | [.sample-command] 20 | ---- 21 | $ bash sample.sh 22 | hello, world 23 | ---- 24 | 25 | ==== 26 | 27 | == Embedded sources and commands example 28 | 29 | This example embeds all the things. 30 | 31 | [.testable-sample] 32 | .Gradle custom logging example 33 | ==== 34 | 35 | .build.gradle 36 | [source,groovy] 37 | ---- 38 | task compile { 39 | doLast { 40 | println "compiling source" 41 | } 42 | } 43 | task testCompile(dependsOn: compile) { 44 | doLast { 45 | println "compiling test source" 46 | } 47 | } 48 | task test(dependsOn: [compile, testCompile]) { 49 | doLast { 50 | println "running unit tests" 51 | } 52 | } 53 | task build(dependsOn: [test]) 54 | ---- 55 | 56 | .init.gradle 57 | [source,groovy] 58 | ---- 59 | useLogger(new CustomEventLogger()) 60 | 61 | class CustomEventLogger extends BuildAdapter implements TaskExecutionListener { 62 | 63 | public void beforeExecute(Task task) { 64 | println "[$task.name]" 65 | } 66 | 67 | public void afterExecute(Task task, TaskState state) { 68 | println() 69 | } 70 | 71 | public void buildFinished(BuildResult result) { 72 | println 'build completed' 73 | if (result.failure != null) { 74 | result.failure.printStackTrace() 75 | } 76 | } 77 | } 78 | ---- 79 | 80 | [.sample-command,allow-disordered-output=true] 81 | ---- 82 | $ gradle -I init.gradle build 83 | 84 | > Task :compile 85 | [compile] 86 | compiling source 87 | 88 | 89 | > Task :testCompile 90 | [testCompile] 91 | compiling test source 92 | 93 | 94 | > Task :test 95 | [test] 96 | running unit tests 97 | 98 | 99 | > Task :build 100 | [build] 101 | 102 | build completed 103 | 3 actionable tasks: 3 executed 104 | ---- 105 | 106 | ==== 107 | 108 | == Multi-step sample 109 | 110 | [.testable-sample] 111 | ==== 112 | 113 | .sample.sh 114 | [source] 115 | ---- 116 | #!/usr/bin/env bash 117 | 118 | echo "dir = `basename $PWD`" 119 | ---- 120 | 121 | Create a directory: 122 | 123 | [.sample-command] 124 | ---- 125 | $ mkdir demo 126 | $ cd demo 127 | ---- 128 | 129 | Run the script: 130 | 131 | [.sample-command] 132 | ---- 133 | $ bash ../sample.sh 134 | dir = demo 135 | ---- 136 | 137 | ==== 138 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/AsciidoctorAnnotationOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Subject 6 | 7 | @Subject(AsciidoctorAnnotationOutputNormalizer) 8 | class AsciidoctorAnnotationOutputNormalizerTest extends Specification { 9 | def "removes Asciidoctor annotation"() { 10 | given: 11 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer() 12 | String input = """ 13 | |./build/install 14 | |├── main 15 | |│   └── debug 16 | |│   ├── building-cpp-applications // <1> 17 | |│   └── lib 18 | |│   └── building-cpp-applications // <2> 19 | |└── test 20 | | ├── building-cpp-applicationsTest // <1> 21 | | └── lib 22 | | └── building-cpp-applicationsTest // <3> 23 | | 24 | |5 directories, 4 files""".stripMargin() 25 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 26 | 27 | expect: 28 | def result = normalizer.normalize(input, executionMetadata) 29 | !result.contains('// <1>') 30 | !result.contains('// <2>') 31 | !result.contains('// <3>') 32 | } 33 | 34 | def "strip trailing whitespace for aligning Asciidoctor annotation"() { 35 | given: 36 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer() 37 | String input = """ 38 | |./build/install 39 | |├── main 40 | |│   └── debug 41 | |│   ├── building-cpp-applications // <1> 42 | |│   └── lib 43 | |│   └── building-cpp-applications // <2> 44 | |└── test 45 | | ├── building-cpp-applicationsTest // <1> 46 | | └── lib 47 | | └── building-cpp-applicationsTest // <3> 48 | | 49 | |5 directories, 4 files""".stripMargin() 50 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 51 | 52 | expect: 53 | !(input =~ /\s+$/).find() 54 | def result = normalizer.normalize(input, executionMetadata) 55 | !result.contains('// <1>') 56 | !(result =~ /\s+$/).find() 57 | } 58 | 59 | def "does not remove leading new lines"() { 60 | given: 61 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer() 62 | String input = """ 63 | |./build/install 64 | |├── main 65 | |│   └── debug 66 | |│   ├── building-cpp-applications // <1> 67 | |│   └── lib 68 | |│   └── building-cpp-applications // <2> 69 | |└── test 70 | | ├── building-cpp-applicationsTest // <1> 71 | | └── lib 72 | | └── building-cpp-applicationsTest // <3> 73 | | 74 | |5 directories, 4 files 75 | |""".stripMargin() 76 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 77 | 78 | expect: 79 | def result = normalizer.normalize(input, executionMetadata) 80 | result.startsWith('\n') 81 | } 82 | 83 | def "does not remove trailing new lines"() { 84 | given: 85 | OutputNormalizer normalizer = new AsciidoctorAnnotationOutputNormalizer() 86 | String input = """ 87 | |./build/install 88 | |├── main 89 | |│   └── debug 90 | |│   ├── building-cpp-applications // <1> 91 | |│   └── lib 92 | |│   └── building-cpp-applications // <2> 93 | |└── test 94 | | ├── building-cpp-applicationsTest // <1> 95 | | └── lib 96 | | └── building-cpp-applicationsTest // <3> 97 | | 98 | |5 directories, 4 files 99 | |""".stripMargin() 100 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 101 | 102 | expect: 103 | def result = normalizer.normalize(input, executionMetadata) 104 | result.endsWith('\n') 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/FileSeparatorOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.normalizer 17 | 18 | import org.gradle.exemplar.executor.ExecutionMetadata 19 | import spock.lang.Specification 20 | 21 | class FileSeparatorOutputNormalizerTest extends Specification { 22 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, Collections.emptyMap()) 23 | FileSeparatorOutputNormalizer normalizer = new FileSeparatorOutputNormalizer() 24 | 25 | def "avoids normalizing strings that aren't file paths"() { 26 | expect: 27 | normalizer.normalize("anything", executionMetadata) == "anything" 28 | normalizer.normalize("", executionMetadata) == "" 29 | normalizer.normalize("foo /--- bar", executionMetadata, '\\' as char) == "foo /--- bar" 30 | normalizer.normalize("foo /--- bar", executionMetadata, '/' as char) == "foo /--- bar" 31 | normalizer.normalize("foo /--- bar", executionMetadata, File.separatorChar) == "foo /--- bar" 32 | } 33 | 34 | def "replaces all file separators in paths to unix-style"() { 35 | expect: 36 | normalizer.normalize("Path C:\\Users\\username\\dir", executionMetadata, '\\' as char) == "Path C:/Users/username/dir" 37 | } 38 | 39 | def "does not remove leading new lines"() { 40 | given: 41 | OutputNormalizer normalizer = new FileSeparatorOutputNormalizer() 42 | String input = """ 43 | |BUILD SUCCESSFUL 44 | |2 actionable tasks: 2 executed 45 | |""".stripMargin() 46 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 47 | 48 | expect: 49 | def result = normalizer.normalize(input, executionMetadata) 50 | result.startsWith('\n') 51 | } 52 | 53 | def "does not remove trailing new lines"() { 54 | given: 55 | OutputNormalizer normalizer = new FileSeparatorOutputNormalizer() 56 | String input = """ 57 | |Path C:\\Users\\username\\dir 58 | |""".stripMargin() 59 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 60 | 61 | expect: 62 | def result = normalizer.normalize(input, executionMetadata) 63 | result.endsWith('\n') 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/LeadingNewLineOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Subject 6 | 7 | @Subject(LeadingNewLineOutputNormalizer) 8 | class LeadingNewLineOutputNormalizerTest extends Specification { 9 | def "can normalize empty output"() { 10 | given: 11 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer() 12 | String input = '' 13 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 14 | 15 | when: 16 | def result = normalizer.normalize(input, executionMetadata) 17 | 18 | then: 19 | noExceptionThrown() 20 | 21 | and: 22 | result == '' 23 | } 24 | 25 | def "can normalize one line of output"() { 26 | given: 27 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer() 28 | String input = 'Some output' 29 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 30 | 31 | when: 32 | def result = normalizer.normalize(input, executionMetadata) 33 | 34 | then: 35 | noExceptionThrown() 36 | 37 | and: 38 | result == 'Some output' 39 | } 40 | 41 | def "does not remove trailing new lines"() { 42 | given: 43 | OutputNormalizer normalizer = new LeadingNewLineOutputNormalizer() 44 | String input = """ 45 | |BUILD SUCCESSFUL 46 | |2 actionable tasks: 2 executed 47 | |""".stripMargin() 48 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 49 | 50 | expect: 51 | def result = normalizer.normalize(input, executionMetadata) 52 | result.endsWith('\n') 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/StripTrailingOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Subject 6 | 7 | @Subject(StripTrailingOutputNormalizer) 8 | class StripTrailingOutputNormalizerTest extends Specification { 9 | def "can remove trailing spaces at the end of each output line"() { 10 | given: 11 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer() 12 | String input = """ 13 | |BUILD SUCCESSFUL 14 | |2 actionable tasks: 2 executed 15 | |""".stripMargin() 16 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 17 | 18 | expect: 19 | def result = normalizer.normalize(input, executionMetadata) 20 | (input =~ /[ ]+$/).find() 21 | !(result =~ /[ ]+$/).find() 22 | } 23 | 24 | def "does not remove leading new lines"() { 25 | given: 26 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer() 27 | String input = """ 28 | |BUILD SUCCESSFUL 29 | |2 actionable tasks: 2 executed 30 | |""".stripMargin() 31 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 32 | 33 | expect: 34 | def result = normalizer.normalize(input, executionMetadata) 35 | result.startsWith('\n') 36 | } 37 | 38 | def "does not remove trailing new lines"() { 39 | given: 40 | OutputNormalizer normalizer = new StripTrailingOutputNormalizer() 41 | String input = """ 42 | |BUILD SUCCESSFUL 43 | |2 actionable tasks: 2 executed 44 | |""".stripMargin() 45 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 46 | 47 | expect: 48 | def result = normalizer.normalize(input, executionMetadata) 49 | result.endsWith('\n') 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/TrailingNewLineOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Subject 6 | 7 | @Subject(TrailingNewLineOutputNormalizer) 8 | class TrailingNewLineOutputNormalizerTest extends Specification { 9 | def "can remove empty line at the end of the output"() { 10 | given: 11 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer() 12 | String input = ''' 13 | |BUILD SUCCESSFUL 14 | |2 actionable tasks: 2 executed 15 | |'''.stripMargin() 16 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 17 | 18 | expect: 19 | def result = normalizer.normalize(input, executionMetadata) 20 | !result.endsWith('\n') 21 | } 22 | 23 | def "can remove multiple empty line at the end of the output"() { 24 | given: 25 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer() 26 | String input = ''' 27 | |BUILD SUCCESSFUL 28 | |2 actionable tasks: 2 executed 29 | | 30 | | 31 | |'''.stripMargin() 32 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 33 | 34 | expect: 35 | def result = normalizer.normalize(input, executionMetadata) 36 | !result.endsWith('\n') 37 | } 38 | 39 | def "can normalize empty output"() { 40 | given: 41 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer() 42 | String input = '' 43 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 44 | 45 | when: 46 | def result = normalizer.normalize(input, executionMetadata) 47 | 48 | then: 49 | noExceptionThrown() 50 | 51 | and: 52 | result == '' 53 | } 54 | 55 | def "can normalize one line of output"() { 56 | given: 57 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer() 58 | String input = 'Some output' 59 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 60 | 61 | when: 62 | def result = normalizer.normalize(input, executionMetadata) 63 | 64 | then: 65 | noExceptionThrown() 66 | 67 | and: 68 | result == 'Some output' 69 | } 70 | 71 | def "does not remove leading new lines"() { 72 | given: 73 | OutputNormalizer normalizer = new TrailingNewLineOutputNormalizer() 74 | String input = """ 75 | |BUILD SUCCESSFUL 76 | |2 actionable tasks: 2 executed 77 | |""".stripMargin() 78 | ExecutionMetadata executionMetadata = new ExecutionMetadata(null, [:]) 79 | 80 | expect: 81 | def result = normalizer.normalize(input, executionMetadata) 82 | result.startsWith('\n') 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/normalizer/WorkingDirectoryOutputNormalizerTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.normalizer 2 | 3 | import org.gradle.exemplar.executor.ExecutionMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Subject 6 | 7 | @Subject(WorkingDirectoryOutputNormalizer) 8 | class WorkingDirectoryOutputNormalizerTest extends Specification { 9 | def "can remove working path"() { 10 | given: 11 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer() 12 | String input = """ 13 | |Some output with a temporary path: /private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663/demo/build/classes/java/main 14 | |BUILD SUCCESSFUL 15 | |2 actionable tasks: 2 executed 16 | |""".stripMargin() 17 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:]) 18 | 19 | expect: 20 | def result = normalizer.normalize(input, executionMetadata) 21 | !result.contains('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663') 22 | result.contains('/working-directory/demo/build/classes/java/main') 23 | } 24 | 25 | def "does not remove leading new lines"() { 26 | given: 27 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer() 28 | String input = """ 29 | |BUILD SUCCESSFUL 30 | |2 actionable tasks: 2 executed 31 | |""".stripMargin() 32 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:]) 33 | 34 | expect: 35 | def result = normalizer.normalize(input, executionMetadata) 36 | result.startsWith('\n') 37 | } 38 | 39 | def "does not remove trailing new lines"() { 40 | given: 41 | OutputNormalizer normalizer = new WorkingDirectoryOutputNormalizer() 42 | String input = """ 43 | |Some output with a temporary path: /private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663/demo/build/classes/java/main 44 | |BUILD SUCCESSFUL 45 | |2 actionable tasks: 2 executed 46 | |""".stripMargin() 47 | ExecutionMetadata executionMetadata = new ExecutionMetadata(new File('/private/var/folders/rg/y7myh0qj1sd58f02v_tgypvh0000gn/T/exemplar1868731005284620663'), [:]) 48 | 49 | expect: 50 | def result = normalizer.normalize(input, executionMetadata) 51 | result.endsWith('\n') 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/rule/SampleTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.rule 2 | 3 | import org.junit.ClassRule 4 | import org.junit.Rule 5 | import org.junit.Test 6 | import org.junit.experimental.runners.Enclosed 7 | import org.junit.rules.RuleChain 8 | import org.junit.rules.TemporaryFolder 9 | import org.junit.rules.TestRule 10 | import org.junit.runner.RunWith 11 | 12 | @RunWith(Enclosed.class) 13 | class SampleTest { 14 | 15 | static class WithTemporaryFolderRule extends SampleTestCases { 16 | 17 | public TemporaryFolder temporaryFolder = new TemporaryFolder() 18 | Sample sample = Sample.from("src/test/samples/gradle") 19 | .into(temporaryFolder) 20 | .withDefaultSample("basic-sample") 21 | 22 | @Rule 23 | public TestRule ruleChain = RuleChain.outerRule(temporaryFolder).around(sample) 24 | } 25 | 26 | static class WithImplicitTemporaryFolder extends SampleTestCases { 27 | @Rule 28 | public Sample sample = Sample.from("src/test/samples/gradle") 29 | .withDefaultSample("basic-sample") 30 | 31 | @Override 32 | Sample getSample() { 33 | return this.sample 34 | } 35 | } 36 | 37 | static class WithExplicitTemporaryFolder extends SampleTestCases { 38 | @ClassRule 39 | public static TemporaryFolder temporaryFolder = new TemporaryFolder() 40 | @Rule 41 | public Sample sample = Sample.from("src/test/samples/gradle") 42 | .intoTemporaryFolder(temporaryFolder.getRoot()) 43 | .withDefaultSample("basic-sample") 44 | 45 | @Override 46 | Sample getSample() { 47 | return this.sample 48 | } 49 | } 50 | 51 | static abstract class SampleTestCases { 52 | abstract Sample getSample() 53 | 54 | @Test 55 | void "copies default sample"() { 56 | File sampleDir = sample.dir 57 | assert sampleDir.directory 58 | assert new File(sampleDir, "build.gradle").file 59 | } 60 | 61 | @UsesSample("composite-sample/basic") 62 | @Test 63 | void "copies sample from annotation"() { 64 | File sampleDir = sample.dir 65 | assert sample.dir.directory 66 | assert new File(sampleDir, "compositeBuildsBasicCli.sample.out").file 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/runner/BrokenSampleDiscoveryIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner 17 | 18 | import org.junit.Rule 19 | import org.junit.rules.TemporaryFolder 20 | import org.junit.platform.engine.discovery.DiscoverySelectors 21 | import org.junit.platform.launcher.Launcher 22 | import org.junit.platform.launcher.LauncherDiscoveryRequest 23 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder 24 | import org.junit.platform.launcher.core.LauncherFactory 25 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener 26 | import spock.lang.Specification 27 | 28 | class BrokenSampleDiscoveryIntegrationTest extends Specification { 29 | @Rule 30 | TemporaryFolder tmpDir = new TemporaryFolder() 31 | 32 | def "start JUnit vintage engine via launcher"() { 33 | given: 34 | def brokenSample = tmpDir.newFile("broken.sample.conf") 35 | brokenSample << """ 36 | executable: sleep 37 | args: 1 38 | expected-output-file: not-exist.sample.out 39 | """.stripMargin() 40 | 41 | def testClass = """ 42 | package org.gradle.exemplar.test; 43 | 44 | import org.gradle.exemplar.test.runner.SamplesRunner; 45 | import org.gradle.exemplar.test.runner.SamplesRoot; 46 | import org.junit.runner.RunWith; 47 | 48 | @RunWith(SamplesRunner.class) 49 | @SamplesRoot("${tmpDir.root.absolutePath}") 50 | public class SimpleJUnit4Test { 51 | } 52 | """ 53 | 54 | def testClassFile = tmpDir.newFile("SimpleJUnit4Test.java") 55 | testClassFile.text = testClass 56 | 57 | def compiler = new GroovyClassLoader() 58 | def compiledClass = compiler.parseClass(testClassFile) 59 | 60 | when: 61 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 62 | .selectors(DiscoverySelectors.selectClass(compiledClass)) 63 | .build() 64 | 65 | Launcher launcher = LauncherFactory.create() 66 | def listener = new SummaryGeneratingListener() 67 | launcher.registerTestExecutionListeners(listener) 68 | 69 | launcher.execute(request) 70 | 71 | then: 72 | listener.summary.testsFailedCount == 1 73 | listener.summary.failures[0].exception.message.contains("Could not read sample definition") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/runner/CollectingNotifier.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.runner 2 | 3 | import org.junit.runner.Description 4 | import org.junit.runner.notification.Failure 5 | import org.junit.runner.notification.RunNotifier 6 | import org.junit.runner.notification.StoppedByUserException 7 | 8 | 9 | class CollectingNotifier extends RunNotifier { 10 | final List tests = [] 11 | final List failures = [] 12 | 13 | @Override 14 | void fireTestStarted(Description description) throws StoppedByUserException { 15 | tests.add(description) 16 | } 17 | 18 | @Override 19 | void fireTestFailure(Failure failure) { 20 | failures.add(failure) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/runner/SamplesRunnerIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.runner 2 | 3 | import org.junit.experimental.categories.Category 4 | import org.junit.runner.Request 5 | import org.junit.runner.RunWith 6 | import spock.lang.Specification 7 | 8 | class SamplesRunnerIntegrationTest extends Specification { 9 | def "runs samples-check CLI samples"() { 10 | def notifier = new CollectingNotifier() 11 | 12 | when: 13 | Request.aClass(HappyDaySamples.class).runner.run(notifier) 14 | 15 | then: 16 | notifier.tests.size() == 2 17 | notifier.tests[0].methodName == 'multi-step_multi-step.sample' 18 | notifier.tests[0].className == HappyDaySamples.class.name 19 | 20 | notifier.tests[1].methodName == 'quickstart_quickstart.sample' 21 | notifier.tests[1].className == HappyDaySamples.class.name 22 | 23 | notifier.failures.empty 24 | } 25 | 26 | @RunWith(SamplesRunner.class) 27 | @SamplesRoot("src/test/samples/cli") 28 | @Category(CoveredByTests) 29 | static class HappyDaySamples {} 30 | 31 | def "can use multi-steps with working directory inside sample"() { 32 | def notifier = new CollectingNotifier() 33 | 34 | when: 35 | Request.aClass(HappyDayWithWorkingDirectorySamples.class).runner.run(notifier) 36 | 37 | then: 38 | notifier.tests.size() == 1 39 | notifier.tests[0].methodName == 'multi-step_multi-step.sample' 40 | notifier.tests[0].className == HappyDayWithWorkingDirectorySamples.class.name 41 | 42 | notifier.failures.empty 43 | } 44 | 45 | @RunWith(SamplesRunner.class) 46 | @SamplesRoot("src/test/samples/cli-with-working-directory") 47 | @Category(CoveredByTests) 48 | static class HappyDayWithWorkingDirectorySamples {} 49 | 50 | def "warn when using working directory after change directory command instruction"() { 51 | def notifier = new CollectingNotifier() 52 | 53 | when: 54 | Request.aClass(HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples.class).runner.run(notifier) 55 | 56 | then: 57 | notifier.tests.size() == 1 58 | notifier.tests[0].methodName == 'multi-step_multi-step.sample' 59 | notifier.tests[0].className == HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples.class.name 60 | 61 | notifier.failures.empty 62 | } 63 | 64 | @RunWith(SamplesRunner.class) 65 | @SamplesRoot("src/test/samples/cli-with-working-directory-and-change-directory") 66 | @Category(CoveredByTests) 67 | static class HappyDayWithWorkingDirectoryAndChangeDirectoryCommandSamples {} 68 | } 69 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/runner/SamplesRunnerSadDayIntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.runner 2 | 3 | import org.junit.Rule 4 | import org.junit.experimental.categories.Category 5 | import org.junit.rules.TemporaryFolder 6 | import org.junit.runner.Request 7 | import org.junit.runner.RunWith 8 | import spock.lang.Specification 9 | 10 | class SamplesRunnerSadDayIntegrationTest extends Specification { 11 | @Rule 12 | TemporaryFolder tmpDir = new TemporaryFolder() 13 | 14 | def "tests fail when command fails"() { 15 | def notifier = new CollectingNotifier() 16 | 17 | when: 18 | Request.aClass(HasBadCommand.class).runner.run(notifier) 19 | 20 | then: 21 | notifier.tests.size() == 1 22 | notifier.tests[0].methodName == '_broken-command.sample' 23 | notifier.tests[0].className == HasBadCommand.class.name 24 | 25 | notifier.failures.size() == 1 26 | notifier.failures[0].description == notifier.tests[0] 27 | 28 | def expectedOutput = """ 29 | Expected sample invocation to succeed but it failed. 30 | Command was: 'bash broken' 31 | Working directory: '.+/_broken-command.sample' 32 | \\[BEGIN OUTPUT\\] 33 | bash: broken: No such file or directory 34 | 35 | \\[END OUTPUT\\] 36 | """.stripIndent(true).trim() 37 | notifier.failures[0].message.trim() ==~ /${expectedOutput}/ 38 | } 39 | 40 | def "tests fail when command produces unexpected output"() { 41 | def notifier = new CollectingNotifier() 42 | 43 | when: 44 | Request.aClass(HasBadOutput.class).runner.run(notifier) 45 | 46 | then: 47 | notifier.tests.size() == 1 48 | notifier.tests[0].methodName == '_broken-output.sample' 49 | notifier.tests[0].className == HasBadOutput.class.name 50 | 51 | notifier.failures.size() == 1 52 | notifier.failures[0].description == notifier.tests[0] 53 | notifier.failures[0].message.trim() == """ 54 | Missing text at line 1. 55 | Expected: not a thing 56 | Actual: thing 57 | Actual output: 58 | thing 59 | """.stripIndent(true).trim() 60 | } 61 | 62 | @SamplesRoot("src/test/resources/broken/command") 63 | @RunWith(SamplesRunner) 64 | @Category(CoveredByTests) 65 | static class HasBadCommand {} 66 | 67 | @SamplesRoot("src/test/resources/broken/output") 68 | @RunWith(SamplesRunner) 69 | @Category(CoveredByTests) 70 | static class HasBadOutput {} 71 | } 72 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/verifier/AnyOrderLineSegmentedOutputVerifierTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.verifier 17 | 18 | import spock.lang.Specification 19 | 20 | class AnyOrderLineSegmentedOutputVerifierTest extends Specification { 21 | private static final String NL = System.getProperty("line.separator") 22 | OutputVerifier verifier = new AnyOrderLineSegmentedOutputVerifier() 23 | 24 | def "checks all expected lines exist in any order with no extra output"() { 25 | given: 26 | String expected = """ 27 | message 1 28 | message 2 29 | """ 30 | String actual = """ 31 | message 2 32 | message 1 33 | """ 34 | 35 | when: 36 | verifier.verify(expected, actual, false) 37 | 38 | then: 39 | notThrown(AssertionError) 40 | } 41 | 42 | def "checks all expected lines exist in any order with extra output"() { 43 | given: 44 | String expected = """ 45 | message 1 46 | message 2 47 | """ 48 | String actual = """ 49 | ` 50 | message 1 51 | 52 | 53 | extra output 54 | """ 55 | 56 | when: 57 | verifier.verify(expected, actual, true) 58 | 59 | then: 60 | AssertionError assertionError = thrown(AssertionError) 61 | assertionError.message.contains("""Line missing from output.${NL}message 2""") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /samples-check/src/test/groovy/org/gradle/exemplar/test/verifier/StrictOrderLineSegmentedOutputVerifierTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.verifier 17 | 18 | import spock.lang.Specification 19 | 20 | class StrictOrderLineSegmentedOutputVerifierTest extends Specification { 21 | private static final String NL = System.getProperty("line.separator") 22 | OutputVerifier verifier = new StrictOrderLineSegmentedOutputVerifier() 23 | 24 | def "checks all expected lines exist in sequential order disallowing extra output"() { 25 | given: 26 | String expected = """ 27 | message 1 28 | message 2 29 | """ 30 | String actual = """ 31 | message 1 32 | message 2 33 | """ 34 | 35 | when: 36 | verifier.verify(expected, actual, false) 37 | 38 | then: 39 | notThrown(AssertionError) 40 | } 41 | 42 | def "checks all expected lines exist in sequential order with extra output"() { 43 | given: 44 | String expected = """ 45 | message 1 46 | message 2 47 | """ 48 | String actual = """ 49 | message 1 50 | message 2 51 | 52 | extra logs 53 | """ 54 | 55 | when: 56 | verifier.verify(expected, actual, true) 57 | 58 | then: 59 | notThrown(AssertionError) 60 | } 61 | 62 | def "checks all expected lines exist in sequential order with extra output at the beginning"() { 63 | given: 64 | String expected = """message 1 65 | message 2 66 | """ 67 | String actual = """ 68 | extra logs 69 | 70 | message 1 71 | message 2 72 | """ 73 | 74 | when: 75 | verifier.verify(expected, actual, true) 76 | 77 | then: 78 | notThrown(AssertionError) 79 | } 80 | 81 | def "fails when expected lines not found while disallowing extra output"() { 82 | given: 83 | String expected = """ 84 | message 1 85 | message 2 86 | """ 87 | String actual = """ 88 | message 2 89 | message 1 90 | """ 91 | 92 | when: 93 | verifier.verify(expected, actual, false) 94 | 95 | then: 96 | AssertionError assertionError = thrown(AssertionError) 97 | assertionError.message.contains("""Unexpected value at line 2.${NL}Expected: message 1${NL}Actual: message 2""") 98 | } 99 | 100 | def "fails with extra output while disallowing extra output"() { 101 | given: 102 | String expected = """ 103 | message 1 104 | message 2 105 | """ 106 | String actual = """ 107 | message 1 108 | message 2 109 | 110 | extra logs 111 | """ 112 | 113 | when: 114 | verifier.verify(expected, actual, false) 115 | 116 | then: 117 | AssertionError assertionError = thrown(AssertionError) 118 | assertionError.message.contains('Extra lines in actual result, starting at line 4.') 119 | } 120 | 121 | def "fails when expected lines not found while allowing extra output"() { 122 | given: 123 | String expected = """message 1 124 | message 2 125 | """ 126 | String actual = """ 127 | extra logs 128 | 129 | message 3 130 | message 4 131 | 132 | extra logs 2 133 | """ 134 | 135 | when: 136 | verifier.verify(expected, actual, true) 137 | 138 | then: 139 | def error = thrown(AssertionError) 140 | error.message.contains('Lines missing from actual result, starting at expected line 0.') 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/CliSamplesRunnerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | // tag::source[] 19 | import org.junit.runner.RunWith; 20 | 21 | @RunWith(SamplesRunner.class) 22 | @SamplesRoot("src/test/samples/cli") 23 | public class CliSamplesRunnerIntegrationTest { 24 | } 25 | // end::source[] 26 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/CoveredByTests.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.runner; 2 | 3 | /** 4 | * Indicates test classes that are exercised by some other test (eg broken tests). 5 | */ 6 | public interface CoveredByTests { 7 | } 8 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/EmbeddedSamplesRunnerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.junit.runner.RunWith; 19 | 20 | @RunWith(GradleEmbeddedSamplesRunner.class) 21 | @SamplesRoot("src/test/docs") 22 | public class EmbeddedSamplesRunnerIntegrationTest { 23 | } 24 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/GradleSamplesRunnerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.test.normalizer.FileSeparatorOutputNormalizer; 19 | import org.gradle.exemplar.test.normalizer.GradleOutputNormalizer; 20 | import org.gradle.exemplar.test.normalizer.JavaObjectSerializationOutputNormalizer; 21 | import org.junit.runner.RunWith; 22 | 23 | @RunWith(GradleSamplesRunner.class) 24 | @SamplesRoot("src/test/samples/gradle") 25 | // tag::sample-output-normalizers[] 26 | @SamplesOutputNormalizers({JavaObjectSerializationOutputNormalizer.class, FileSeparatorOutputNormalizer.class, GradleOutputNormalizer.class}) 27 | // end::sample-output-normalizers[] 28 | public class GradleSamplesRunnerIntegrationTest { 29 | } 30 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/SampleModifierIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.test.runner; 17 | 18 | import org.gradle.exemplar.test.runner.modifiers.ExtraCommandArgumentsSampleModifier; 19 | import org.junit.runner.RunWith; 20 | 21 | @RunWith(GradleSamplesRunner.class) 22 | @SamplesRoot("src/test/samples/customization") 23 | @SampleModifiers({ExtraCommandArgumentsSampleModifier.class}) 24 | public class SampleModifierIntegrationTest { 25 | } 26 | -------------------------------------------------------------------------------- /samples-check/src/test/java/org/gradle/exemplar/test/runner/modifiers/ExtraCommandArgumentsSampleModifier.java: -------------------------------------------------------------------------------- 1 | package org.gradle.exemplar.test.runner.modifiers; 2 | 3 | import org.gradle.exemplar.model.Command; 4 | import org.gradle.exemplar.model.Sample; 5 | import org.gradle.exemplar.test.runner.SampleModifier; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class ExtraCommandArgumentsSampleModifier implements SampleModifier { 11 | @Override 12 | public Sample modify(Sample sampleIn) { 13 | List newCommands = new ArrayList<>(); 14 | for (Command command : sampleIn.getCommands()) { 15 | List args = new ArrayList<>(command.getArgs()); 16 | args.add("printProperty"); 17 | args.add("-DmyProp=myValue"); 18 | newCommands.add(command.toBuilder().setArgs(args).build()); 19 | } 20 | return new Sample(sampleIn.getId(), sampleIn.getProjectDir(), newCommands); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples-check/src/test/resources/broken/command/broken-command.sample.conf: -------------------------------------------------------------------------------- 1 | executable = bash 2 | args = broken 3 | -------------------------------------------------------------------------------- /samples-check/src/test/resources/broken/output/broken-output.sample.conf: -------------------------------------------------------------------------------- 1 | executable = echo 2 | args = thing 3 | expected-output-file: sample.out 4 | -------------------------------------------------------------------------------- /samples-check/src/test/resources/broken/output/sample.out: -------------------------------------------------------------------------------- 1 | not a thing 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/multi-step.sample.conf: -------------------------------------------------------------------------------- 1 | commands: [{ 2 | executable = "mkdir" 3 | execution-subdirectory = "workDir" 4 | args = "demo" 5 | }, { 6 | executable = "cd" 7 | args = "workDir" 8 | }, { 9 | executable = "bash" 10 | execution-subdirectory = "demo" 11 | args = "../../sample.sh" 12 | expected-output-file = "sample.out" 13 | }] 14 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/sample.out: -------------------------------------------------------------------------------- 1 | dir = demo 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/sample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "dir = `basename $PWD`" 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/workDir/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/exemplar/02b6c53c344ca792be380c92eb20682a89eb288a/samples-check/src/test/samples/cli-with-working-directory-and-change-directory/multi-step/workDir/.placeholder -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory/multi-step/multi-step.sample.conf: -------------------------------------------------------------------------------- 1 | commands: [{ 2 | executable = "mkdir" 3 | execution-subdirectory = "workDir" 4 | args = "demo" 5 | }, { 6 | executable = "bash" 7 | execution-subdirectory = "workDir/demo" 8 | args = "../../sample.sh" 9 | expected-output-file = "sample.out" 10 | }] 11 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory/multi-step/sample.out: -------------------------------------------------------------------------------- 1 | dir = demo 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory/multi-step/sample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "dir = `basename $PWD`" 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli-with-working-directory/multi-step/workDir/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/exemplar/02b6c53c344ca792be380c92eb20682a89eb288a/samples-check/src/test/samples/cli-with-working-directory/multi-step/workDir/.placeholder -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/multi-step/multi-step.sample.conf: -------------------------------------------------------------------------------- 1 | commands: [{ 2 | executable = "mkdir" 3 | args = "demo" 4 | }, { 5 | executable = "cd" 6 | args = "demo" 7 | }, { 8 | executable = "bash" 9 | args = "../sample.sh" 10 | expected-output-file = "sample.out" 11 | }] 12 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/multi-step/sample.out: -------------------------------------------------------------------------------- 1 | dir = demo 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/multi-step/sample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "dir = `basename $PWD`" 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/quickstart/quickstart.sample.conf: -------------------------------------------------------------------------------- 1 | executable: bash 2 | args: sample.sh 3 | expected-output-file: quickstart.sample.out 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/quickstart/quickstart.sample.out: -------------------------------------------------------------------------------- 1 | hello, world 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/cli/quickstart/sample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "hello, world" 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/customization/customization-sample/build.gradle: -------------------------------------------------------------------------------- 1 | task hello { 2 | doLast { 3 | println("hello") 4 | } 5 | } 6 | 7 | task printProperty { 8 | doLast { 9 | println("myProp: ${System.getProperty("myProp")}") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/customization/customization-sample/hello.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradle 2 | args: hello 3 | flags: -q 4 | expected-output-file: hello.sample.out 5 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/customization/customization-sample/hello.sample.out: -------------------------------------------------------------------------------- 1 | hello 2 | myProp: myValue 3 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/basic-sample/build.gradle: -------------------------------------------------------------------------------- 1 | task hello { 2 | doLast { 3 | println("hello, world") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/basic-sample/hello.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradle 2 | args: hello 3 | flags: -q 4 | expected-output-file: hello.sample.out 5 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/basic-sample/hello.sample.out: -------------------------------------------------------------------------------- 1 | hello, world 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/build-init-sample/build-init.sample.conf: -------------------------------------------------------------------------------- 1 | commands = [ 2 | { 3 | executable = "mkdir" 4 | args = "demo" 5 | }, 6 | { 7 | executable = "cd" 8 | args = "demo" 9 | }, 10 | { 11 | executable = "gradle" 12 | args = "init" 13 | }, 14 | { 15 | executable = "gradle" 16 | args = "projects" 17 | expected-output-file = "sample.out" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/build-init-sample/sample.out: -------------------------------------------------------------------------------- 1 | 2 | > Task :projects 3 | 4 | Projects: 5 | 6 | ------------------------------------------------------------ 7 | Root project 'demo' 8 | ------------------------------------------------------------ 9 | 10 | Root project 'demo' 11 | No sub-projects 12 | 13 | To see a list of the tasks of a project, run gradle :tasks 14 | For example, try running gradle :tasks 15 | 16 | BUILD SUCCESSFUL in 0s 17 | 1 actionable task: 1 executed 18 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/composite/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'idea' 2 | 3 | defaultTasks 'run' 4 | 5 | // START SNIPPET run 6 | task run { 7 | dependsOn gradle.includedBuild('my-app').task(':run') 8 | } 9 | // END SNIPPET run 10 | 11 | task checkAll { 12 | dependsOn gradle.includedBuild('my-app').task(':check') 13 | dependsOn gradle.includedBuild('my-utils').task(':number-utils:check') 14 | dependsOn gradle.includedBuild('my-utils').task(':string-utils:check') 15 | } 16 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/composite/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='adhoc' 2 | 3 | includeBuild '../my-app' 4 | includeBuild '../my-utils' 5 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/compositeBuildsBasicCli.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradle 2 | execution-subdirectory: my-app 3 | args: run 4 | flags: "--include-build=../my-utils" 5 | expect-failure: false 6 | expected-output-file: "compositeBuildsBasicCli.sample.out" 7 | allow-additional-output: true 8 | allow-disordered-output: true 9 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/compositeBuildsBasicCli.sample.out: -------------------------------------------------------------------------------- 1 | > Task :processResources NO-SOURCE 2 | > Task :my-utils:string-utils:compileJava 3 | > Task :my-utils:string-utils:processResources NO-SOURCE 4 | > Task :my-utils:string-utils:classes 5 | > Task :my-utils:string-utils:jar 6 | > Task :my-utils:number-utils:compileJava 7 | > Task :my-utils:number-utils:processResources NO-SOURCE 8 | > Task :my-utils:number-utils:classes 9 | > Task :my-utils:number-utils:jar 10 | > Task :compileJava 11 | > Task :classes 12 | 13 | > Task :run 14 | The answer is 42 15 | 16 | BUILD SUCCESSFUL in 0s 17 | 6 actionable tasks: 6 executed 18 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'application' 3 | apply plugin: 'idea' 4 | 5 | group "org.sample" 6 | version "1.0" 7 | 8 | mainClassName = "org.sample.myapp.Main" 9 | 10 | dependencies { 11 | implementation "org.sample:number-utils:1.0" 12 | implementation "org.sample:string-utils:1.0" 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-app/settings-composite.gradle: -------------------------------------------------------------------------------- 1 | // Needs to be used with --settings-file settings-composite.gradle 2 | 3 | rootProject.name = 'my-app' 4 | 5 | includeBuild '../my-utils' 6 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-app/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'my-app' 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-app/src/main/java/org/sample/myapp/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.sample.myapp; 18 | 19 | import org.sample.numberutils.Numbers; 20 | import org.sample.stringutils.Strings; 21 | 22 | public class Main { 23 | 24 | public static void main(String... args) { 25 | new Main().printAnswer(); 26 | } 27 | 28 | public void printAnswer() { 29 | String output = Strings.concat(" The answer is ", Numbers.add(19, 23)); 30 | System.out.println(output); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'idea' 2 | 3 | subprojects { 4 | apply plugin: 'java' 5 | apply plugin: 'idea' 6 | 7 | group "org.sample" 8 | version "1.0" 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | } 14 | 15 | project(":string-utils") { 16 | dependencies { 17 | implementation "org.apache.commons:commons-lang3:3.12.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/number-utils/src/main/java/org/sample/numberutils/Numbers.java: -------------------------------------------------------------------------------- 1 | package org.sample.numberutils; 2 | 3 | public class Numbers { 4 | public static int add(int left, int right) { return left + right; } 5 | } 6 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'my-utils' 2 | 3 | include 'number-utils', 'string-utils' 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/composite-sample/basic/my-utils/string-utils/src/main/java/org/sample/stringutils/Strings.java: -------------------------------------------------------------------------------- 1 | package org.sample.stringutils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | public class Strings { 6 | public static String concat(Object left, Object right) { 7 | return strip(left) + " " + strip(right); 8 | } 9 | 10 | private static String strip(Object val) { 11 | return StringUtils.strip(String.valueOf(val)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/groovy/build.gradle: -------------------------------------------------------------------------------- 1 | tasks.create("sayHello") { 2 | doLast { 3 | println('Hello, world!') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/groovy/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dsl-sample' 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/helpTask.out: -------------------------------------------------------------------------------- 1 | 2 | > Task :sayHello 3 | Hello, world! 4 | 5 | BUILD SUCCESSFUL in 0s 6 | 1 actionable task: 1 executed -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/helpTask.sample.conf: -------------------------------------------------------------------------------- 1 | commands: [{ 2 | executable: gradle 3 | execution-subdirectory: groovy 4 | args: sayHello 5 | expected-output-file: helpTask.out 6 | }, { 7 | executable: gradle 8 | execution-subdirectory: kotlin 9 | args: sayHello 10 | expected-output-file: helpTask.out 11 | }] 12 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | tasks.create("sayHello") { 2 | doLast { 3 | println("Hello, world!") 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/dual-dsl-sample/kotlin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "dsl-sample" 2 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/multi-step-sample/build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.work.ChangeType 2 | import org.gradle.work.Incremental 3 | import org.gradle.work.InputChanges 4 | 5 | def inputsDir = layout.projectDirectory.dir("inputs") 6 | 7 | task originalInputs() { 8 | doLast { 9 | inputsDir.asFile.mkdir() 10 | inputsDir.file('1.txt').asFile.text = 'Content for file 1.' 11 | inputsDir.file('2.txt').asFile.text = 'Content for file 2.' 12 | inputsDir.file('3.txt').asFile.text = 'Content for file 3.' 13 | } 14 | } 15 | 16 | // START SNIPPET updated-inputs 17 | task updateInputs() { 18 | doLast { 19 | inputsDir.file('1.txt').asFile.text = 'Changed content for existing file 1.' 20 | inputsDir.file('4.txt').asFile.text = 'Content for new file 4.' 21 | } 22 | } 23 | // END SNIPPET updated-inputs 24 | 25 | // START SNIPPET removed-input 26 | task removeInput() { 27 | doLast { 28 | inputsDir.file('3.txt').asFile.delete() 29 | } 30 | } 31 | // END SNIPPET removed-input 32 | 33 | // START SNIPPET removed-output 34 | task removeOutput() { 35 | doLast { 36 | layout.buildDirectory.file('outputs/1.txt').get().asFile.delete() 37 | } 38 | } 39 | // END SNIPPET removed-output 40 | 41 | // START SNIPPET reverse 42 | task incrementalReverse(type: IncrementalReverseTask) { 43 | inputDir = inputsDir.asFileTree 44 | outputDir = layout.buildDirectory.dir('outputs') 45 | inputProperty = project.properties['taskInputProperty'] ?: 'original' 46 | } 47 | // END SNIPPET reverse 48 | 49 | // START SNIPPET incremental-task 50 | class IncrementalReverseTask extends DefaultTask { 51 | @InputFiles 52 | @SkipWhenEmpty 53 | FileCollection inputDir 54 | 55 | @OutputDirectory 56 | Provider outputDir 57 | 58 | @Input 59 | def inputProperty 60 | 61 | @TaskAction 62 | void execute(InputChanges inputs) { 63 | println inputs.incremental ? 'CHANGED inputs considered out of date' 64 | : 'ALL inputs considered out of date' 65 | // START SNIPPET handle-non-incremental-inputs 66 | if (!inputs.incremental) 67 | project.delete(outputDir.get().asFile.listFiles()) 68 | // END SNIPPET handle-non-incremental-inputs 69 | 70 | if (inputs.incremental) { 71 | inputs.getFileChanges(inputDir).each { change -> 72 | // START SNIPPET out-of-date-inputs 73 | if (change.changeType == ChangeType.MODIFIED && change.file.file) { 74 | println "out of date: ${change.file.name}" 75 | def targetFile = new File(outputDir.get().asFile, change.file.name) 76 | targetFile.text = change.file.text.reverse() 77 | } 78 | // END SNIPPET out-of-date-inputs 79 | // START SNIPPET added-inputs 80 | if (change.changeType == ChangeType.ADDED && change.file.file) { 81 | println "added: ${change.file.name}" 82 | def targetFile = new File(outputDir.get().asFile, change.file.name) 83 | targetFile.text = change.file.text.reverse() 84 | } 85 | // END SNIPPET added-inputs 86 | 87 | // START SNIPPET removed-inputs 88 | if (change.changeType == ChangeType.REMOVED) { 89 | println "removed: ${change.file.name}" 90 | def targetFile = new File(outputDir.get().asFile, change.file.name) 91 | targetFile.delete() 92 | } 93 | // END SNIPPET removed-inputs 94 | } 95 | } 96 | } 97 | } 98 | // END SNIPPET incremental-task 99 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/multi-step-sample/incrementalTaskRemovedOutput.out: -------------------------------------------------------------------------------- 1 | CHANGED inputs considered out of date 2 | out of date: 1.txt 3 | added: 4.txt 4 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/multi-step-sample/incrementalTaskRemovedOutput.sample.conf: -------------------------------------------------------------------------------- 1 | commands: [{ 2 | executable: gradle 3 | args: originalInputs 4 | }, { 5 | executable: gradle 6 | args: incrementalReverse 7 | expected-output-file: originalInputs.out 8 | allow-additional-output: true 9 | }, { 10 | executable: gradle 11 | args: removeOutput updateInputs 12 | }, { 13 | executable: gradle 14 | args: incrementalReverse 15 | expected-output-file: incrementalTaskRemovedOutput.out 16 | allow-disordered-output: true 17 | allow-additional-output: true 18 | }] 19 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/multi-step-sample/originalInputs.out: -------------------------------------------------------------------------------- 1 | > Task :incrementalReverse 2 | ALL inputs considered out of date 3 | -------------------------------------------------------------------------------- /samples-check/src/test/samples/gradle/multi-step-sample/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'incremental-task' 2 | -------------------------------------------------------------------------------- /samples-discovery/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("exemplar.java-conventions") 3 | id("exemplar.publishing-conventions") 4 | } 5 | 6 | dependencies { 7 | compileOnly(libs.jsr305) 8 | implementation(libs.asciidoctorj) 9 | implementation(libs.commons.io) 10 | implementation(libs.commons.lang3) 11 | implementation(libs.typesafe.config) 12 | testImplementation(libs.groovy) 13 | testImplementation(libs.bundles.spock) 14 | } 15 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/InvalidSampleException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar; 17 | 18 | public class InvalidSampleException extends RuntimeException { 19 | public InvalidSampleException(String message) { 20 | super(message); 21 | } 22 | 23 | public InvalidSampleException(String message, Exception cause) { 24 | super(message, cause); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/loader/CommandsParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader; 17 | 18 | import com.typesafe.config.*; 19 | import org.gradle.exemplar.InvalidSampleException; 20 | import org.gradle.exemplar.model.Command; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.nio.charset.Charset; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.util.*; 29 | 30 | public class CommandsParser { 31 | private static final String EXECUTABLE = "executable"; 32 | private static final String COMMANDS = "commands"; 33 | private static final String EXECUTION_SUBDIRECTORY = "execution-subdirectory"; 34 | private static final String ARGS = "args"; 35 | private static final String FLAGS = "flags"; 36 | private static final String EXPECT_FAILURE = "expect-failure"; 37 | private static final String ALLOW_ADDITIONAL_OUTPUT = "allow-additional-output"; 38 | private static final String ALLOW_DISORDERED_OUTPUT = "allow-disordered-output"; 39 | private static final String EXPECTED_OUTPUT_FILE = "expected-output-file"; 40 | private static final String USER_INPUTS = "user-inputs"; 41 | 42 | public static List parse(final File sampleConfigFile) { 43 | try { 44 | final Config sampleConfig = ConfigFactory.parseFile(sampleConfigFile, ConfigParseOptions.defaults().setAllowMissing(false)).resolve(); 45 | final File sampleProjectDir = sampleConfigFile.getParentFile(); 46 | 47 | List commands = new ArrayList<>(); 48 | // Allow a single command to be specified without an enclosing list 49 | if (sampleConfig.hasPath(EXECUTABLE)) { 50 | commands.add(parseCommand(sampleConfig, sampleProjectDir)); 51 | } else if (sampleConfig.hasPath(COMMANDS)) { 52 | for (Config stepConfig : sampleConfig.getConfigList(COMMANDS)) { 53 | commands.add(parseCommand(stepConfig, sampleProjectDir)); 54 | } 55 | } else { 56 | throw new InvalidSampleException("A sample must be defined with an 'executable' or 'commands'"); 57 | } 58 | 59 | return commands; 60 | } catch (Exception e) { 61 | throw new InvalidSampleException(String.format("Could not read sample definition from %s.", sampleConfigFile), e); 62 | } 63 | } 64 | 65 | private static Command parseCommand(final Config commandConfig, final File sampleProjectDir) { 66 | // NOTE: A user must specify an executable. This prevents unexpected behavior when an empty or unexpected file is accidentally loaded 67 | String executable; 68 | try { 69 | executable = commandConfig.getString(EXECUTABLE); 70 | } catch (ConfigException e) { 71 | throw new InvalidSampleException("'executable' field cannot be empty", e); 72 | } 73 | final String executionDirectory = ConfigUtil.string(commandConfig, EXECUTION_SUBDIRECTORY, null); 74 | final List commands = ConfigUtil.strings(commandConfig, ARGS, new ArrayList()); 75 | final List flags = ConfigUtil.strings(commandConfig, FLAGS, new ArrayList()); 76 | String expectedOutput = null; 77 | if (commandConfig.hasPath(EXPECTED_OUTPUT_FILE)) { 78 | final File expectedOutputFile = new File(sampleProjectDir, commandConfig.getString(EXPECTED_OUTPUT_FILE)); 79 | try { 80 | final Path path = Paths.get(expectedOutputFile.getAbsolutePath()); 81 | expectedOutput = new String(Files.readAllBytes(path), Charset.forName("UTF-8")); 82 | } catch (IOException e) { 83 | throw new InvalidSampleException("Could not read sample output file " + expectedOutputFile.getAbsolutePath(), e); 84 | } 85 | } 86 | 87 | final boolean expectFailures = ConfigUtil.booleanValue(commandConfig, EXPECT_FAILURE, false); 88 | final boolean allowAdditionalOutput = ConfigUtil.booleanValue(commandConfig, ALLOW_ADDITIONAL_OUTPUT, false); 89 | final boolean allowDisorderedOutput = ConfigUtil.booleanValue(commandConfig, ALLOW_DISORDERED_OUTPUT, false); 90 | final List userInputs = ConfigUtil.strings(commandConfig, USER_INPUTS, Collections.emptyList()); 91 | 92 | return new Command(executable, executionDirectory, commands, flags, expectedOutput, expectFailures, allowAdditionalOutput, allowDisorderedOutput, userInputs); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/loader/ConfigUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader; 17 | 18 | import com.typesafe.config.Config; 19 | import com.typesafe.config.ConfigValue; 20 | 21 | import java.io.File; 22 | import java.util.*; 23 | 24 | class ConfigUtil { 25 | public static Map map(Config config, String key, Map defaultValues) { 26 | if (config.hasPath(key)) { 27 | Map props = new LinkedHashMap<>(); 28 | for (Map.Entry entry : config.getConfig(key).entrySet()) { 29 | props.put(entry.getKey(), entry.getValue().unwrapped().toString()); 30 | } 31 | return props; 32 | } else { 33 | return defaultValues; 34 | } 35 | } 36 | 37 | public static boolean booleanValue(Config config, String key, boolean defaultValue) { 38 | if (config.hasPath(key)) { 39 | return Boolean.valueOf(config.getString(key)); 40 | } else { 41 | return defaultValue; 42 | } 43 | } 44 | 45 | public static String string(Config config, String key, String defaultValue) { 46 | if (config.hasPath(key)) { 47 | return config.getString(key); 48 | } else { 49 | return defaultValue; 50 | } 51 | } 52 | 53 | public static List strings(Config config, String key, List defaults) { 54 | if (config.hasPath(key)) { 55 | Object value = config.getAnyRef(key); 56 | if (value instanceof List) { 57 | List result = new ArrayList<>(); 58 | for (Object o : (List) value) { 59 | result.add(o.toString()); 60 | } 61 | return result; 62 | } else if (value.toString().length() > 0) { 63 | return Arrays.asList(value.toString().split(" ")); 64 | } 65 | } 66 | return defaults; 67 | } 68 | 69 | public static File file(Config config, File projectDir, String key, File defaultValue) { 70 | String fileName = ConfigUtil.string(config, key, null); 71 | if (fileName == null) { 72 | return defaultValue; 73 | } else { 74 | return new File(projectDir, fileName); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/loader/SamplesDiscovery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader; 17 | 18 | import org.apache.commons.io.FileUtils; 19 | import org.apache.commons.io.FilenameUtils; 20 | import org.gradle.exemplar.loader.asciidoctor.AsciidoctorSamplesDiscovery; 21 | import org.gradle.exemplar.model.Command; 22 | import org.gradle.exemplar.model.InvalidSample; 23 | import org.gradle.exemplar.model.Sample; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.*; 28 | 29 | public class SamplesDiscovery { 30 | public static List externalSamples(File rootSamplesDir) { 31 | // The .sample.conf suffix makes it clear that this is a HOCON file specifically for samples 32 | return filteredExternalSamples(rootSamplesDir, new String[]{"sample.conf"}, true); 33 | } 34 | 35 | public static List filteredExternalSamples(File rootSamplesDir, String[] fileExtensions, boolean recursive) { 36 | Collection sampleConfigFiles = FileUtils.listFiles(rootSamplesDir, fileExtensions, recursive); 37 | 38 | List samples = new ArrayList<>(); 39 | for (File sampleConfigFile : sampleConfigFiles) { 40 | final String id = generateSampleId(rootSamplesDir, sampleConfigFile); 41 | try { 42 | final List commands = CommandsParser.parse(sampleConfigFile); 43 | // FIXME: Currently the temp directory used when running samples-check has a different name. 44 | // This causes Gradle project names to differ when one is not explicitly set in settings.gradle. This should be preserved. 45 | final File sampleProjectDir = sampleConfigFile.getParentFile(); 46 | samples.add(new Sample(id, sampleProjectDir, commands)); 47 | } catch (Exception e) { 48 | samples.add(new InvalidSample(id, e)); 49 | } 50 | } 51 | // Always return (and test) samples in a fixed order 52 | sortSamples(samples); 53 | 54 | return samples; 55 | } 56 | 57 | public static List embeddedSamples(File asciidocSrcDir) throws IOException { 58 | return filteredEmbeddedSamples(asciidocSrcDir, new String[]{"adoc", "asciidoc"}, true); 59 | } 60 | 61 | public static List filteredEmbeddedSamples(File rootSamplesDir, String[] fileExtensions, boolean recursive) throws IOException { 62 | Collection sampleConfigFiles = FileUtils.listFiles(rootSamplesDir, fileExtensions, recursive); 63 | 64 | List samples = new ArrayList<>(); 65 | for (File sampleConfigFile : sampleConfigFiles) { 66 | samples.addAll(AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(sampleConfigFile)); 67 | } 68 | sortSamples(samples); 69 | 70 | return samples; 71 | } 72 | 73 | private static String generateSampleId(File rootSamplesDir, File scenarioFile) { 74 | String prefix = rootSamplesDir 75 | .toPath() 76 | .relativize(scenarioFile.getParentFile().toPath()) 77 | .toString() 78 | .replaceAll("[/\\\\]", "_"); 79 | return prefix + "_" + FilenameUtils.removeExtension(scenarioFile.getName()); 80 | } 81 | 82 | private static void sortSamples(List samples) { 83 | Collections.sort(samples, new SampleComparator()); 84 | } 85 | 86 | private static class SampleComparator implements Comparator { 87 | @Override 88 | public int compare(Sample s1, Sample s2) { 89 | return s1.getId().compareTo(s2.getId()); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/loader/asciidoctor/AsciidoctorCommandsDiscovery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader.asciidoctor; 17 | 18 | import org.asciidoctor.Asciidoctor; 19 | import org.asciidoctor.OptionsBuilder; 20 | import org.asciidoctor.ast.AbstractBlock; 21 | import org.asciidoctor.ast.Block; 22 | import org.asciidoctor.ast.Document; 23 | import org.asciidoctor.ast.ListImpl; 24 | import org.gradle.exemplar.InvalidSampleException; 25 | import org.gradle.exemplar.model.Command; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.util.ArrayDeque; 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Queue; 36 | import java.util.function.Consumer; 37 | 38 | import static org.asciidoctor.OptionsBuilder.options; 39 | 40 | public class AsciidoctorCommandsDiscovery { 41 | 42 | private static final String COMMAND_PREFIX = "$ "; 43 | 44 | public static List extractFromAsciidoctorFile(File documentFile) throws IOException { 45 | return extractFromAsciidoctorFile(documentFile, it -> {}); 46 | } 47 | 48 | public static List extractFromAsciidoctorFile(File documentFile, Consumer action) throws IOException { 49 | Asciidoctor asciidoctor = Asciidoctor.Factory.create(); 50 | 51 | try { 52 | OptionsBuilder options = options(); 53 | action.accept(options); 54 | 55 | Document document = asciidoctor.loadFile(documentFile, options.asMap()); 56 | return extractAsciidocCommands(document); 57 | } finally { 58 | asciidoctor.shutdown(); 59 | } 60 | } 61 | 62 | private static List extractAsciidocCommands(AbstractBlock testableSampleBlock) { 63 | List commands = new ArrayList<>(); 64 | Queue queue = new ArrayDeque<>(); 65 | queue.add(testableSampleBlock); 66 | while (!queue.isEmpty()) { 67 | AbstractBlock node = queue.poll(); 68 | if (node instanceof ListImpl) { 69 | queue.addAll(((ListImpl) node).getItems()); 70 | } else { 71 | for (AbstractBlock child : node.getBlocks()) { 72 | if (child.isBlock() && child.hasRole("sample-command")) { 73 | parseEmbeddedCommand((Block) child, commands); 74 | } else { 75 | queue.offer(child); 76 | } 77 | } 78 | } 79 | } 80 | 81 | return commands; 82 | } 83 | 84 | private static void parseEmbeddedCommand(Block block, List commands) { 85 | Map attributes = block.getAttributes(); 86 | String[] lines = block.source().split("\r?\n"); 87 | int pos = 0; 88 | 89 | do { 90 | pos = parseOneCommand(lines, pos, attributes, commands); 91 | } while (pos < lines.length); 92 | } 93 | 94 | private static int parseOneCommand(String[] lines, int pos, Map attributes, List commands) { 95 | String commandLine = lines[pos]; 96 | if (!commandLine.startsWith(COMMAND_PREFIX)) { 97 | throw new InvalidSampleException("Inline sample command " + commandLine); 98 | } 99 | 100 | String[] commandLineWords = commandLine.substring(COMMAND_PREFIX.length()).split("\\s+"); 101 | String executable = commandLineWords[0]; 102 | 103 | List args = Collections.emptyList(); 104 | if (commandLineWords.length > 1) { 105 | args = Arrays.asList(Arrays.copyOfRange(commandLineWords, 1, commandLineWords.length)); 106 | } 107 | 108 | StringBuilder expectedOutput = new StringBuilder(); 109 | int nextCommand = pos + 1; 110 | while (nextCommand < lines.length && !lines[nextCommand].startsWith(COMMAND_PREFIX)) { 111 | if (nextCommand > pos + 1) { 112 | expectedOutput.append("\n"); 113 | } 114 | expectedOutput.append(lines[nextCommand]); 115 | nextCommand++; 116 | } 117 | 118 | Command command = new Command(executable, 119 | null, 120 | args, 121 | Collections.emptyList(), 122 | expectedOutput.toString(), 123 | attributes.containsKey("expect-failure"), 124 | attributes.containsKey("allow-additional-output"), 125 | attributes.containsKey("allow-disordered-output"), 126 | attributes.containsKey("user-inputs") ? toUserInputs(attributes.get("user-inputs").toString()) : Collections.emptyList()); 127 | commands.add(command); 128 | return nextCommand; 129 | } 130 | 131 | private static List toUserInputs(String value) { 132 | return Arrays.asList(value.split("\\|", -1)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/model/Command.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.model; 17 | 18 | import javax.annotation.Nonnull; 19 | import javax.annotation.Nullable; 20 | import java.util.List; 21 | 22 | public class Command { 23 | private final String executable; 24 | private final String executionSubdirectory; 25 | private final List args; 26 | private final List flags; 27 | private final String expectedOutput; 28 | private final boolean expectFailure; 29 | private final boolean allowAdditionalOutput; 30 | private final boolean allowDisorderedOutput; 31 | private final List userInputs; 32 | 33 | public Command(@Nonnull String executable, @Nullable String executionDirectory, List args, List flags, @Nullable String expectedOutput, boolean expectFailure, boolean allowAdditionalOutput, boolean allowDisorderedOutput, List userInputs) { 34 | this.executable = executable; 35 | this.executionSubdirectory = executionDirectory; 36 | this.args = args; 37 | this.flags = flags; 38 | this.expectedOutput = expectedOutput; 39 | this.expectFailure = expectFailure; 40 | this.allowAdditionalOutput = allowAdditionalOutput; 41 | this.allowDisorderedOutput = allowDisorderedOutput; 42 | this.userInputs = userInputs; 43 | } 44 | 45 | @Nonnull 46 | public String getExecutable() { 47 | return executable; 48 | } 49 | 50 | @Nullable 51 | public String getExecutionSubdirectory() { 52 | return executionSubdirectory; 53 | } 54 | 55 | public List getArgs() { 56 | return args; 57 | } 58 | 59 | public List getFlags() { 60 | return flags; 61 | } 62 | 63 | @Nullable 64 | public String getExpectedOutput() { 65 | return expectedOutput; 66 | } 67 | 68 | /** 69 | * @return true if executing the scenario build is expected to fail. 70 | */ 71 | public boolean isExpectFailure() { 72 | return expectFailure; 73 | } 74 | 75 | /** 76 | * @return true if output lines other than those provided are allowed. 77 | */ 78 | public boolean isAllowAdditionalOutput() { 79 | return allowAdditionalOutput; 80 | } 81 | 82 | /** 83 | * @return true if actual output lines can differ in order from expected. 84 | */ 85 | public boolean isAllowDisorderedOutput() { 86 | return allowDisorderedOutput; 87 | } 88 | 89 | /** 90 | * @return a list of user inputs to provide to the command 91 | */ 92 | public List getUserInputs() { 93 | return userInputs; 94 | } 95 | 96 | public Builder toBuilder() { 97 | return new Builder(getExecutable(), 98 | getExecutionSubdirectory(), 99 | getArgs(), 100 | getFlags(), 101 | getExpectedOutput(), 102 | isExpectFailure(), 103 | isAllowAdditionalOutput(), 104 | isAllowDisorderedOutput(), 105 | getUserInputs()); 106 | } 107 | 108 | public static class Builder { 109 | private String executable; 110 | private String executionSubdirectory; 111 | private List args; 112 | private List flags; 113 | private String expectedOutput; 114 | private boolean expectFailure; 115 | private boolean allowAdditionalOutput; 116 | private boolean allowDisorderedOutput; 117 | private List userInputs; 118 | 119 | private Builder(String executable, String executionDirectory, List args, List flags, String expectedOutput, boolean expectFailure, boolean allowAdditionalOutput, boolean allowDisorderedOutput, List userInputs) { 120 | this.executable = executable; 121 | this.executionSubdirectory = executionDirectory; 122 | this.args = args; 123 | this.flags = flags; 124 | this.expectedOutput = expectedOutput; 125 | this.expectFailure = expectFailure; 126 | this.allowAdditionalOutput = allowAdditionalOutput; 127 | this.allowDisorderedOutput = allowDisorderedOutput; 128 | this.userInputs = userInputs; 129 | } 130 | 131 | public Builder setExecutable(String executable) { 132 | this.executable = executable; 133 | return this; 134 | } 135 | 136 | public Builder setExecutionSubdirectory(String executionSubdirectory) { 137 | this.executionSubdirectory = executionSubdirectory; 138 | return this; 139 | } 140 | 141 | public Builder setArgs(List args) { 142 | this.args = args; 143 | return this; 144 | } 145 | 146 | public Builder setFlags(List flags) { 147 | this.flags = flags; 148 | return this; 149 | } 150 | 151 | public Builder setExpectedOutput(String expectedOutput) { 152 | this.expectedOutput = expectedOutput; 153 | return this; 154 | } 155 | 156 | public Builder setExpectFailure(boolean expectFailure) { 157 | this.expectFailure = expectFailure; 158 | return this; 159 | } 160 | 161 | public Builder setAllowAdditionalOutput(boolean allowAdditionalOutput) { 162 | this.allowAdditionalOutput = allowAdditionalOutput; 163 | return this; 164 | } 165 | 166 | public Builder setAllowDisorderedOutput(boolean allowDisorderedOutput) { 167 | this.allowDisorderedOutput = allowDisorderedOutput; 168 | return this; 169 | } 170 | 171 | public Command build() { 172 | return new Command(executable, executionSubdirectory, args, flags, expectedOutput, expectFailure, allowAdditionalOutput, allowDisorderedOutput, userInputs); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/model/InvalidSample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.model; 17 | 18 | import java.util.Collections; 19 | 20 | public class InvalidSample extends Sample { 21 | private final Exception exception; 22 | 23 | public InvalidSample(String id, Exception exception) { 24 | super(id, null, Collections.emptyList()); 25 | this.exception = exception; 26 | } 27 | 28 | public Exception getException() { 29 | return exception; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples-discovery/src/main/java/org/gradle/exemplar/model/Sample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.model; 17 | 18 | import java.io.File; 19 | import java.util.List; 20 | 21 | public class Sample { 22 | private final String id; 23 | private final File projectDir; 24 | private final List commands; 25 | 26 | public Sample(String id, File projectDir, List commands) { 27 | this.id = id; 28 | this.projectDir = projectDir; 29 | this.commands = commands; 30 | } 31 | 32 | public String getId() { 33 | return id; 34 | } 35 | 36 | public File getProjectDir() { 37 | return projectDir; 38 | } 39 | 40 | public List getCommands() { 41 | return commands; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples-discovery/src/test/groovy/org/gradle/exemplar/loader/SamplesDiscoveryTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader 17 | 18 | import org.gradle.exemplar.model.Sample 19 | import org.junit.Rule 20 | import org.junit.rules.TemporaryFolder 21 | import spock.lang.Specification 22 | 23 | class SamplesDiscoveryTest extends Specification { 24 | @Rule 25 | TemporaryFolder tmpDir = new TemporaryFolder() 26 | 27 | def "discovers nested samples"() { 28 | given: 29 | tmpDir.newFolder("basic-sample") 30 | tmpDir.newFile("basic-sample/default.sample.conf") << "executable: help" 31 | tmpDir.newFolder("advanced-sample", "nested") 32 | tmpDir.newFile("advanced-sample/shallow.sample.conf") << "commands: [{executable: foo}]" 33 | // tmpDir.newFile("advanced-sample/nested/crazy.sample.conf") << "commands: [{executable: build}, {executable: cleanup}]" 34 | 35 | when: 36 | Collection samples = SamplesDiscovery.externalSamples(tmpDir.root) 37 | 38 | then: 39 | samples.size() == 2 40 | } 41 | 42 | def "allows custom file filter"() { 43 | given: 44 | tmpDir.newFolder("first-sample") 45 | tmpDir.newFile("first-sample/default.sample") << "executable: help" 46 | tmpDir.newFolder("src", "play") 47 | tmpDir.newFile("src/play/bogus.conf") << "I'm not a sample file" 48 | 49 | when: 50 | Collection samples = SamplesDiscovery.filteredExternalSamples(tmpDir.root, ["sample"].toArray() as String[], true) 51 | 52 | then: 53 | samples.size() == 1 54 | samples[0].id == "first-sample_default" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples-discovery/src/test/groovy/org/gradle/exemplar/loader/asciidoctor/AsciidoctorCommandsDiscoveryTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader.asciidoctor 17 | 18 | import org.asciidoctor.AttributesBuilder 19 | import org.asciidoctor.SafeMode 20 | import org.gradle.exemplar.model.Command 21 | import org.junit.Rule 22 | import org.junit.rules.TemporaryFolder 23 | import spock.lang.Specification 24 | import spock.lang.Subject 25 | 26 | @Subject(AsciidoctorCommandsDiscovery) 27 | class AsciidoctorCommandsDiscoveryTest extends Specification { 28 | @Rule 29 | TemporaryFolder tmpDir = new TemporaryFolder() 30 | 31 | def "discovers samples inside an asciidoctor file with sources inline"() { 32 | given: 33 | def file = tmpDir.newFile('sample.adoc') << ''' 34 | |= Document Title 35 | | 36 | |[.sample-command,allow-disordered-output=true] 37 | |---- 38 | |$ ruby hello.rb 39 | | 40 | |hello, world 41 | | 42 | |some more output 43 | |---- 44 | |'''.stripMargin() 45 | 46 | when: 47 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 48 | 49 | then: 50 | commands.size() == 1 51 | def command = commands.get(0) 52 | command.executable == 'ruby' 53 | command.args == ['hello.rb'] 54 | command.allowDisorderedOutput 55 | command.expectedOutput == ''' 56 | |hello, world 57 | | 58 | |some more output'''.stripMargin() 59 | } 60 | 61 | def "sample may include multiple sample-command blocks"() { 62 | given: 63 | def file = tmpDir.newFile('sample.adoc') << ''' 64 | |= Document Title 65 | | 66 | |Run this first: 67 | | 68 | |[.sample-command] 69 | |---- 70 | |$ ruby hello.rb 71 | |hello, world 72 | |---- 73 | | 74 | |Then do this: 75 | | 76 | |[.sample-command] 77 | |---- 78 | |$ mkdir some-dir 79 | |---- 80 | |'''.stripMargin() 81 | 82 | when: 83 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 84 | 85 | then: 86 | commands.size() == 2 87 | def command = commands.get(0) 88 | command.executable == 'ruby' 89 | command.args == ['hello.rb'] 90 | command.expectedOutput == 'hello, world' 91 | 92 | def command2 = commands.get(1) 93 | command2.executable == 'mkdir' 94 | command2.args == ['some-dir'] 95 | command2.expectedOutput.empty 96 | } 97 | 98 | def "sample-command block may include multiple commands"() { 99 | given: 100 | def file = tmpDir.newFile('sample.adoc') << ''' 101 | |= Document Title 102 | | 103 | |Run this first: 104 | | 105 | |[.sample-command] 106 | |---- 107 | |$ ruby hello.rb 108 | | 109 | |hello, world 110 | | 111 | |$ mkdir some-dir 112 | |$ cd some-dir 113 | |---- 114 | |'''.stripMargin() 115 | 116 | when: 117 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 118 | 119 | then: 120 | commands.size() == 3 121 | def command = commands.get(0) 122 | command.executable == 'ruby' 123 | command.args == ['hello.rb'] 124 | command.expectedOutput == ''' 125 | |hello, world 126 | |'''.stripMargin() 127 | 128 | def command2 = commands.get(1) 129 | command2.executable == 'mkdir' 130 | command2.args == ['some-dir'] 131 | command2.expectedOutput.empty 132 | 133 | def command3 = commands.get(2) 134 | command3.executable == 'cd' 135 | command3.args == ['some-dir'] 136 | command3.expectedOutput.empty 137 | } 138 | 139 | def "can include data from attributes"() { 140 | given: 141 | def file = tmpDir.newFile('sample.adoc') << ''' 142 | |= Document Title 143 | | 144 | |Run this first: 145 | | 146 | |[listing.terminal.sample-command] 147 | |---- 148 | |$ pwd 149 | |include::{sampleoutputdir}/pwd-output.txt[] 150 | |---- 151 | |'''.stripMargin() 152 | def outputDir = tmpDir.newFolder('output') 153 | tmpDir.newFile('output/pwd-output.txt').text = "${tmpDir.root.getAbsolutePath()}\n" 154 | 155 | when: 156 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) { 157 | it.attributes(AttributesBuilder.attributes().attribute('sampleoutputdir', outputDir.getAbsolutePath())).safe(SafeMode.UNSAFE) 158 | } 159 | 160 | then: 161 | commands.size() == 1 162 | def command = commands.get(0) 163 | command.executable == 'pwd' 164 | command.args == [] 165 | command.expectedOutput == tmpDir.root.getAbsolutePath() 166 | } 167 | 168 | def "can extract commands when using Asciidoctor callout"() { 169 | given: 170 | def file = tmpDir.newFile('sample.adoc') << ''' 171 | |= Document Title 172 | | 173 | |[listing.terminal.sample-command] 174 | |---- 175 | |$ ./command 176 | |Some output // <1> 177 | |---- 178 | |<1> Some callout 179 | |'''.stripMargin() 180 | 181 | when: 182 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 183 | 184 | then: 185 | commands.size() == 1 186 | def command = commands.get(0) 187 | command.executable == './command' 188 | command.args == [] 189 | command.expectedOutput == 'Some output // <1>' 190 | } 191 | 192 | def "can extract empty user inputs when none is specified"() { 193 | given: 194 | def file = tmpDir.newFile('sample.adoc') << ''' 195 | |= Document Title 196 | | 197 | |[listing.terminal.sample-command] 198 | |---- 199 | |$ ./command 200 | |Some output 201 | |---- 202 | |'''.stripMargin() 203 | 204 | when: 205 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 206 | 207 | then: 208 | commands.size() == 1 209 | def command = commands.get(0) 210 | command.userInputs.size() == 0 211 | } 212 | 213 | def "can extract user inputs to the command"() { 214 | given: 215 | def file = tmpDir.newFile('sample.adoc') << ''' 216 | |= Document Title 217 | | 218 | |[listing.terminal.sample-command,user-inputs="1||yes"] 219 | |---- 220 | |$ ./command 221 | |Some output 222 | |---- 223 | |'''.stripMargin() 224 | 225 | when: 226 | Collection commands = AsciidoctorCommandsDiscovery.extractFromAsciidoctorFile(file) 227 | 228 | then: 229 | commands.size() == 1 230 | def command = commands.get(0) 231 | command.userInputs.size() == 3 232 | command.userInputs.get(0) == '1' 233 | command.userInputs.get(1) == '' 234 | command.userInputs.get(2) == 'yes' 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /samples-discovery/src/test/groovy/org/gradle/exemplar/loader/asciidoctor/AsciidoctorSamplesDiscoveryTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.loader.asciidoctor 17 | 18 | import org.asciidoctor.AttributesBuilder 19 | import org.asciidoctor.SafeMode 20 | import org.gradle.exemplar.model.Sample 21 | import org.junit.Rule 22 | import org.junit.rules.TemporaryFolder 23 | import spock.lang.Specification 24 | 25 | class AsciidoctorSamplesDiscoveryTest extends Specification { 26 | @Rule 27 | TemporaryFolder tmpDir = new TemporaryFolder() 28 | 29 | def "discovers samples inside an asciidoctor file with sources inline"() { 30 | given: 31 | def file = tmpDir.newFile('sample.adoc') << ''' 32 | |= Document Title 33 | | 34 | |.Sample title 35 | |==== 36 | |[.testable-sample] 37 | |===== 38 | |.hello.rb 39 | |[source,ruby] 40 | |---- 41 | |target = "world" 42 | |puts "hello, #{target}" 43 | |---- 44 | | 45 | |[.sample-command,allow-disordered-output=true] 46 | |---- 47 | |$ ruby hello.rb 48 | | 49 | |hello, world 50 | | 51 | |some more output 52 | |---- 53 | |===== 54 | |==== 55 | |'''.stripMargin() 56 | 57 | when: 58 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) 59 | 60 | then: 61 | samples.size() == 1 62 | def commands = samples.get(0).commands 63 | 64 | and: 65 | commands.size() == 1 66 | def command = commands.get(0) 67 | command.executable == 'ruby' 68 | command.args == ['hello.rb'] 69 | command.allowDisorderedOutput 70 | command.expectedOutput == ''' 71 | |hello, world 72 | | 73 | |some more output'''.stripMargin() 74 | } 75 | 76 | def "discovers samples inside an asciidoctor file with sources included"() { 77 | given: 78 | tmpDir.newFolder('src', 'samples', 'bash') 79 | tmpDir.newFile('src/samples/bash/script.sh') << ''' 80 | |#!/usr/bin/env bash 81 | | 82 | |echo "Hello world" 83 | |'''.stripMargin() 84 | def file = tmpDir.newFile('sample.adoc') << ''' 85 | |= Document title 86 | | 87 | |.Sample title 88 | |==== 89 | |[.testable-sample,dir="src/samples/bash"] 90 | |===== 91 | |.script.sh 92 | |[source,bash] 93 | |---- 94 | |include::src/samples/bash/script.sh[] 95 | |---- 96 | | 97 | |[.sample-command] 98 | |---- 99 | |$ bash script.sh 100 | |Hello world 101 | |---- 102 | |===== 103 | |==== 104 | |'''.stripMargin() 105 | 106 | when: 107 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) 108 | 109 | then: 110 | samples.size() == 1 111 | samples.get(0).projectDir.toString() == 'src/samples/bash' 112 | def commands = samples.get(0).commands 113 | 114 | and: 115 | commands.size() == 1 116 | def command = commands.get(0) 117 | command.executable == 'bash' 118 | command.args == ['script.sh'] 119 | command.expectedOutput == 'Hello world' 120 | } 121 | 122 | def "sample may include multiple sample-command blocks"() { 123 | given: 124 | def file = tmpDir.newFile('sample.adoc') << ''' 125 | |= Document Title 126 | | 127 | |.Sample title 128 | |[.testable-sample] 129 | |==== 130 | | 131 | |Run this first: 132 | | 133 | |[.sample-command] 134 | |---- 135 | |$ ruby hello.rb 136 | |hello, world 137 | |---- 138 | | 139 | |Then do this: 140 | | 141 | |[.sample-command] 142 | |---- 143 | |$ mkdir some-dir 144 | |---- 145 | |==== 146 | |'''.stripMargin() 147 | 148 | when: 149 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) 150 | 151 | then: 152 | samples.size() == 1 153 | def commands = samples.get(0).commands 154 | 155 | and: 156 | commands.size() == 2 157 | def command = commands.get(0) 158 | command.executable == 'ruby' 159 | command.args == ['hello.rb'] 160 | command.expectedOutput == 'hello, world' 161 | 162 | def command2 = commands.get(1) 163 | command2.executable == 'mkdir' 164 | command2.args == ['some-dir'] 165 | command2.expectedOutput.empty 166 | } 167 | 168 | def "sample-command block may include multiple commands"() { 169 | given: 170 | def file = tmpDir.newFile('sample.adoc') << ''' 171 | |= Document Title 172 | | 173 | |.Sample title 174 | |[.testable-sample] 175 | |==== 176 | | 177 | |Run this first: 178 | | 179 | |[.sample-command] 180 | |---- 181 | |$ ruby hello.rb 182 | | 183 | |hello, world 184 | | 185 | |$ mkdir some-dir 186 | |$ cd some-dir 187 | |---- 188 | |==== 189 | |'''.stripMargin() 190 | 191 | when: 192 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) 193 | 194 | then: 195 | samples.size() == 1 196 | def commands = samples.get(0).commands 197 | 198 | and: 199 | commands.size() == 3 200 | def command = commands.get(0) 201 | command.executable == "ruby" 202 | command.args == ["hello.rb"] 203 | command.expectedOutput == """ 204 | |hello, world 205 | |""".stripMargin() 206 | 207 | def command2 = commands.get(1) 208 | command2.executable == 'mkdir' 209 | command2.args == ['some-dir'] 210 | command2.expectedOutput.empty 211 | 212 | def command3 = commands.get(2) 213 | command3.executable == 'cd' 214 | command3.args == ['some-dir'] 215 | command3.expectedOutput.empty 216 | } 217 | 218 | def "can include data from attributes"() { 219 | given: 220 | def file = tmpDir.newFile('sample.adoc') << ''' 221 | |= Document Title 222 | | 223 | |.Sample title 224 | |[.testable-sample] 225 | |==== 226 | | 227 | |Run this first: 228 | | 229 | |[listing.terminal.sample-command] 230 | |---- 231 | |$ pwd 232 | |include::{sampleoutputdir}/pwd-output.txt[] 233 | |---- 234 | |==== 235 | |'''.stripMargin() 236 | def outputDir = tmpDir.newFolder('output') 237 | tmpDir.newFile('output/pwd-output.txt').text = "${tmpDir.root.getAbsolutePath()}\n" 238 | 239 | when: 240 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) { 241 | it.attributes(AttributesBuilder.attributes().attribute('sampleoutputdir', outputDir.getAbsolutePath())).safe(SafeMode.UNSAFE) 242 | } 243 | 244 | then: 245 | samples.size() == 1 246 | def commands = samples.get(0).commands 247 | 248 | and: 249 | commands.size() == 1 250 | def command = commands.get(0) 251 | command.executable == 'pwd' 252 | command.args == [] 253 | command.expectedOutput == tmpDir.root.getAbsolutePath() 254 | } 255 | 256 | def "can extract commands when using Asciidoctor callout"() { 257 | given: 258 | def file = tmpDir.newFile('sample.adoc') << ''' 259 | |= Document Title 260 | | 261 | |[listing.terminal.testable-sample.sample-command] 262 | |---- 263 | |$ ./command 264 | |Some output // <1> 265 | |---- 266 | |<1> Some callout 267 | |'''.stripMargin() 268 | 269 | when: 270 | Collection samples = AsciidoctorSamplesDiscovery.extractFromAsciidoctorFile(file) 271 | 272 | then: 273 | samples.size() == 1 274 | def commands = samples.get(0).commands 275 | 276 | and: 277 | commands.size() == 1 278 | def command = commands.get(0) 279 | command.executable == './command' 280 | command.args == [] 281 | command.expectedOutput == 'Some output // <1>' 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /samples-discovery/src/test/groovy/org/gradle/exemplar/model/CommandsParserTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.gradle.exemplar.model 17 | 18 | import org.gradle.exemplar.InvalidSampleException 19 | import org.gradle.exemplar.loader.CommandsParser 20 | import org.junit.Rule 21 | import org.junit.rules.TemporaryFolder 22 | import spock.lang.Specification 23 | 24 | class CommandsParserTest extends Specification { 25 | @Rule 26 | TemporaryFolder tmpDir = new TemporaryFolder() 27 | File sampleConfigFile 28 | 29 | def setup() { 30 | sampleConfigFile = tmpDir.newFile("default.sample.conf") 31 | } 32 | 33 | def "fails fast when config file is badly formed"() { 34 | given: 35 | sampleConfigFile << "broken!" 36 | 37 | when: 38 | CommandsParser.parse(sampleConfigFile) 39 | 40 | then: 41 | def e = thrown(InvalidSampleException) 42 | e.message == "Could not read sample definition from ${sampleConfigFile}." 43 | e.cause != null 44 | } 45 | 46 | def "fails fast given no executable or commands array specified"() { 47 | given: 48 | sampleConfigFile << "bogus { unexpected: true }" 49 | tmpDir.newFile("bogus.sample.out").createNewFile() 50 | 51 | when: 52 | CommandsParser.parse(sampleConfigFile) 53 | 54 | then: 55 | def e = thrown(InvalidSampleException) 56 | e.message == "Could not read sample definition from ${sampleConfigFile}." 57 | e.cause.message == "A sample must be defined with an 'executable' or 'commands'" 58 | } 59 | 60 | def "fails fast when no executable for command specified"() { 61 | given: 62 | sampleConfigFile << """ 63 | commands: [{ }, { }] 64 | """ 65 | 66 | when: 67 | CommandsParser.parse(sampleConfigFile) 68 | 69 | then: 70 | def e = thrown(InvalidSampleException) 71 | e.message == "Could not read sample definition from ${sampleConfigFile}." 72 | e.cause.message == "'executable' field cannot be empty" 73 | } 74 | 75 | def "provides reasonable defaults for command config"() { 76 | given: 77 | sampleConfigFile << "executable: hello" 78 | 79 | when: 80 | List commands = CommandsParser.parse(sampleConfigFile) 81 | Command command = commands[0] 82 | 83 | then: 84 | commands.size() == 1 85 | command.executable == "hello" 86 | command.executionSubdirectory == null 87 | command.args == [] 88 | command.flags == [] 89 | !command.expectFailure 90 | command.expectedOutput == null 91 | !command.allowAdditionalOutput 92 | !command.allowDisorderedOutput 93 | } 94 | 95 | def "loads custom values for all command config options"() { 96 | given: 97 | sampleConfigFile << """ 98 | executable: gradle 99 | execution-subdirectory: subproj 100 | args: build 101 | flags: -I init.gradle.kts 102 | expect-failure: true 103 | allow-additional-output: true 104 | allow-disordered-output: true 105 | expected-output-file: customLoggerKts.out 106 | """ 107 | File expectedOutputFile = tmpDir.newFile("customLoggerKts.out") 108 | expectedOutputFile << "> Task :build" 109 | 110 | when: 111 | List commands = CommandsParser.parse(sampleConfigFile) 112 | Command command = commands[0] 113 | 114 | then: 115 | commands.size() == 1 116 | command.executable == "gradle" 117 | command.executionSubdirectory == "subproj" 118 | command.args == ["build"] 119 | command.flags == ["-I", "init.gradle.kts"] 120 | command.expectFailure 121 | command.expectedOutput == "> Task :build" 122 | command.allowAdditionalOutput 123 | command.allowDisorderedOutput 124 | } 125 | 126 | def "parses multiple steps"() { 127 | given: 128 | sampleConfigFile << """ 129 | commands: [{ 130 | executable: gradle 131 | execution-subdirectory: subproj 132 | args: produce 133 | },{ 134 | executable: gradle 135 | execution-subdirectory: otherproject 136 | args: consume 137 | flags: --quiet 138 | expect-failure: true 139 | allow-additional-output: true 140 | allow-disordered-output: true 141 | expected-output-file: customLoggerKts.out 142 | }] 143 | """ 144 | File expectedOutputFile = tmpDir.newFile("customLoggerKts.out") 145 | expectedOutputFile << "> Task :build" 146 | 147 | when: 148 | List commands = CommandsParser.parse(sampleConfigFile) 149 | 150 | then: 151 | commands.size() == 2 152 | 153 | Command firstCommand = commands[0] 154 | firstCommand.executable == "gradle" 155 | firstCommand.executionSubdirectory == "subproj" 156 | firstCommand.args == ["produce"] 157 | !firstCommand.expectFailure 158 | 159 | Command secondCommand = commands[1] 160 | secondCommand.executable == "gradle" 161 | secondCommand.executionSubdirectory == "otherproject" 162 | secondCommand.args == ["consume"] 163 | secondCommand.flags == ["--quiet"] 164 | secondCommand.expectFailure 165 | secondCommand.expectedOutput == "> Task :build" 166 | secondCommand.allowAdditionalOutput 167 | secondCommand.allowDisorderedOutput 168 | } 169 | 170 | def "loads included config"() { 171 | given: 172 | File normalizersConfigFile = tmpDir.newFile("normalizers.conf") 173 | normalizersConfigFile << """ 174 | flags: --init-script foo.bar.kts 175 | """ 176 | 177 | sampleConfigFile << """ 178 | executable: gradle 179 | args: build 180 | include file("${normalizersConfigFile.absolutePath.replace((char) '\\', (char) '/')}") 181 | """ 182 | tmpDir.newFile("default.sample.out").createNewFile() 183 | 184 | when: 185 | List commands = CommandsParser.parse(sampleConfigFile) 186 | Command command = commands[0] 187 | 188 | then: 189 | commands.size() == 1 190 | command.executable == "gradle" 191 | command.args == ["build"] 192 | command.flags == ["--init-script", "foo.bar.kts"] 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.gradle.develocity") version "4.0.1" 3 | id("io.github.gradle.gradle-enterprise-conventions-plugin") version "0.10.3" 4 | } 5 | 6 | rootProject.name = "exemplar" 7 | 8 | include("samples-discovery") 9 | include("samples-check") 10 | include("docs") 11 | --------------------------------------------------------------------------------