diff --git a/nebula-archrules-gradle-plugin/build.gradle.kts b/nebula-archrules-gradle-plugin/build.gradle.kts index 2787623..79a8858 100644 --- a/nebula-archrules-gradle-plugin/build.gradle.kts +++ b/nebula-archrules-gradle-plugin/build.gradle.kts @@ -6,6 +6,10 @@ description = "Plugins for authoring and running Nebula ArchRules" repositories { mavenCentral() } +dependencies { + testImplementation("net.javacrumbs.json-unit:json-unit-assertj:5.0.0") + testImplementation("org.json:json:20250517") +} gradlePlugin { plugins { create("library") { diff --git a/nebula-archrules-gradle-plugin/gradle.lockfile b/nebula-archrules-gradle-plugin/gradle.lockfile index f5092cf..8f3fada 100644 --- a/nebula-archrules-gradle-plugin/gradle.lockfile +++ b/nebula-archrules-gradle-plugin/gradle.lockfile @@ -2,10 +2,18 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. cglib:cglib-nodep:3.2.2=integTestRuntimeClasspath,testRuntimeClasspath +com.jayway.jsonpath:json-path:2.9.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.netflix.nebula:nebula-test:11.7.1=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.15.11=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.7=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.javacrumbs.json-unit:json-unit-assertj:5.0.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.javacrumbs.json-unit:json-unit-core:5.0.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.javacrumbs.json-unit:json-unit-json-path:5.0.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.minidev:accessors-smart:2.5.0=integTestRuntimeClasspath,testRuntimeClasspath +net.minidev:json-smart:2.5.0=integTestRuntimeClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.assertj:assertj-core:3.27.3=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.27.6=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest-core:3.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains.kotlin:kotlin-assignment-compiler-plugin-embeddable:2.2.0=kotlinCompilerPluginClasspathIntegTest,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest org.jetbrains.kotlin:kotlin-build-tools-api:2.2.0=kotlinBuildToolsApiClasspath org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.0=kotlinBuildToolsApiClasspath @@ -24,6 +32,7 @@ org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.0=kotlinBuildToolsApiClasspath,kot org.jetbrains.kotlin:kotlin-stdlib:2.2.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathIntegTest,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testCompileClasspath,testRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath org.jetbrains:annotations:13.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathIntegTest,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,testCompileClasspath,testRuntimeClasspath +org.json:json:20250517=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.jspecify:jspecify:1.0.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.12.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.12.2=integTestRuntimeClasspath,testRuntimeClasspath @@ -34,4 +43,6 @@ org.junit.platform:junit-platform-engine:1.14.0=integTestCompileClasspath,integT org.junit.platform:junit-platform-launcher:1.14.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.objenesis:objenesis:2.4=integTestRuntimeClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm:9.3=integTestRuntimeClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.11=integTestRuntimeClasspath,testRuntimeClasspath empty=annotationProcessor,integTestAnnotationProcessor,integTestKotlinScriptDefExtensions,kotlinScriptDefExtensions,runtimeClasspath,testAnnotationProcessor,testKotlinScriptDefExtensions diff --git a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesLibraryPluginTest.kt b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesLibraryPluginTest.kt index eb91cfa..6c0497a 100644 --- a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesLibraryPluginTest.kt +++ b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesLibraryPluginTest.kt @@ -1,10 +1,17 @@ package com.netflix.nebula.archrules.gradle +import nebula.test.dsl.* import nebula.test.dsl.TestKitAssertions.assertThat +import net.javacrumbs.jsonunit.assertj.assertThatJson import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File class ArchrulesLibraryPluginTest { + @TempDir + lateinit var projectDir: File @Test fun `plugin registers library dependency`() { @@ -18,4 +25,71 @@ class ArchrulesLibraryPluginTest { assertThat(coreLibrary).isNotNull assertThat(coreLibrary!!.version).isEqualTo("latest.release") } + + @Test + fun `plugin produces maven publication`() { + val runner = testProject(projectDir) { + settings { + name("library-with-rules") + } + rootProject { + group("com.example") + // a library that contains production code and rules to go along with it + plugins { + id("java-library") + id("com.netflix.nebula.archrules.library") + id("maven-publish") + } + repositories { + maven("https://netflixoss.jfrog.io/artifactory/gradle-plugins") + mavenCentral() + } + declareMavenPublication() + src { + main { + exampleLibraryClass() + } + sourceSet("archRules") { + exampleDeprecatedArchRule() + } + } + } + } + + val result = runner.run( + "build", + "archRulesJar", + "generateMetadataFileForMavenPublication", // to test publication metadata without actually publishing, + "-Pversion=0.0.1" + ) + + assertThat(result.task(":compileArchRulesJava")) + .`as`("compile task runs for the archRules source set") + .hasOutcome(TaskOutcome.SUCCESS) + assertThat(result.task(":archRulesJar")) + .hasOutcome(TaskOutcome.SUCCESS) + assertThat(result) + .hasNoMutableStateWarnings() + .hasNoDeprecationWarnings() + + assertThat(projectDir.resolve("build/libs/library-with-rules-0.0.1.jar")) + .`as`("Library Jar is created") + .exists() + assertThat(projectDir.resolve("build/libs/library-with-rules-0.0.1-archrules.jar")) + .`as`("ArchRules Jar is created") + .exists() + + val moduleMetadata = projectDir.resolve("build/publications/maven/module.json") + assertThat(moduleMetadata) + .`as`("Gradle Module Metadata is created") + .exists() + val moduleMetadataJson = moduleMetadata.readText() + println(moduleMetadataJson) + assertThatJson(moduleMetadataJson) + .inPath("$.variants[?(@.name=='runtimeElements')].files[0]") + .isArray + .first().isObject + .containsEntry("name", "library-with-rules-0.0.1.jar") + + } } \ No newline at end of file diff --git a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/IntegrationTest.kt b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/IntegrationTest.kt index dba5780..11e094a 100644 --- a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/IntegrationTest.kt +++ b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/IntegrationTest.kt @@ -28,51 +28,10 @@ internal class IntegrationTest { } src { main { - java( - "com/example/library/LibraryClass.java", - //language=java - """ -package com.example.library; - -public class LibraryClass { - -} - """ - ) + exampleLibraryClass() } sourceSet("archRules") { - java( - "com/example/library/LibraryArchRules.java", - //language=java - """ -package com.example.library; - -import com.netflix.nebula.archrules.core.ArchRulesService; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.Priority; -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; -import java.util.Map; -import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target; -import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner; -import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; - -public class LibraryArchRules implements ArchRulesService { - private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW) - .noClasses() - .should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class))) - .orShould().accessTargetWhere(target(annotatedWith(Deprecated.class))) - .orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class) - .allowEmptyShould(true) - .as("No code should reference deprecated APIs") - .because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked"); - - @Override - public Map getRules() { - return Map.of("deprecated", noDeprecated); - } -} -""" - ) + exampleDeprecatedArchRule() } } } @@ -88,15 +47,10 @@ public class LibraryArchRules implements ArchRulesService { } } - val result = runner.run("check", "archRulesJar") { + val result = runner.run("check") { withGradleVersion(gradleVersion.version) } - assertThat(result.task(":library-with-rules:compileArchRulesJava")) - .`as`("compile task runs for the archRules source set") - .hasOutcome(TaskOutcome.SUCCESS) - assertThat(result.task(":library-with-rules:archRulesJar")) - .hasOutcome(TaskOutcome.SUCCESS) assertThat(result.task(":library-with-rules:check")) .hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE) assertThat(result.task(":code-to-check:check")) @@ -104,12 +58,5 @@ public class LibraryArchRules implements ArchRulesService { assertThat(result) .hasNoMutableStateWarnings() .hasNoDeprecationWarnings() - - assertThat(projectDir.resolve("library-with-rules/build/libs/library-with-rules.jar")) - .`as`("Library Jar is created") - .exists() - assertThat(projectDir.resolve("library-with-rules/build/libs/library-with-rules-archrules.jar")) - .`as`("ArchRules Jar is created") - .exists() } } \ No newline at end of file diff --git a/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/TestKitDslExtensions.kt b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/TestKitDslExtensions.kt new file mode 100644 index 0000000..74b5bbd --- /dev/null +++ b/nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/TestKitDslExtensions.kt @@ -0,0 +1,68 @@ +package com.netflix.nebula.archrules.gradle + +import nebula.test.dsl.ProjectBuilder +import nebula.test.dsl.SourceSetBuilder + +fun ProjectBuilder.declareMavenPublication() { + //language=kotlin + rawBuildScript( + """ +publishing { + publications { + create("maven") { + from(components["java"]) + } + } +} +""" + ) +} + +fun SourceSetBuilder.exampleLibraryClass() { + java( + "com/example/library/LibraryClass.java", + //language=java + """ +package com.example.library; + +public class LibraryClass { + +} +""" + ) +} + +fun SourceSetBuilder.exampleDeprecatedArchRule() { + java( + "com/example/library/LibraryArchRules.java", + //language=java + """ +package com.example.library; + +import com.netflix.nebula.archrules.core.ArchRulesService; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.Priority; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import java.util.Map; +import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target; +import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner; +import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; + +public class LibraryArchRules implements ArchRulesService { + private final ArchRule noDeprecated = ArchRuleDefinition.priority(Priority.LOW) + .noClasses() + .should().accessTargetWhere(targetOwner(annotatedWith(Deprecated.class))) + .orShould().accessTargetWhere(target(annotatedWith(Deprecated.class))) + .orShould().dependOnClassesThat().areAnnotatedWith(Deprecated.class) + .allowEmptyShould(true) + .as("No code should reference deprecated APIs") + .because("usage of deprecated APIs introduces risk that future upgrades and migrations will be blocked"); + + @Override + public Map getRules() { + return Map.of("deprecated", noDeprecated); + } +} +""" + ) +} \ No newline at end of file