2626import java .util .LinkedHashSet ;
2727import java .util .Map ;
2828import java .util .Set ;
29+ import java .util .function .UnaryOperator ;
2930
3031import org .gradle .api .tasks .SourceSet ;
3132import org .gradle .testkit .runner .BuildResult ;
3940import org .junit .jupiter .params .ParameterizedTest ;
4041import org .junit .jupiter .params .provider .EnumSource ;
4142
43+ import org .springframework .boot .build .architecture .annotations .TestConditionalOnClass ;
4244import org .springframework .util .ClassUtils ;
4345import org .springframework .util .FileSystemUtils ;
4446import org .springframework .util .StringUtils ;
@@ -180,7 +182,7 @@ void whenClassCallsObjectsRequireNonNullWithMessageShouldFailAndWriteReport(Task
180182 void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport (
181183 Task task ) throws IOException {
182184 prepareTask (task , "objects/requireNonNullWithString" );
183- build (this .gradleBuild .withProhibitObjectsRequireNonNull (task , false ), task );
185+ build (this .gradleBuild .withProhibitObjectsRequireNonNull (false ), task );
184186 }
185187
186188 @ ParameterizedTest (name = "{0}" )
@@ -195,7 +197,7 @@ void whenClassCallsObjectsRequireNonNullWithSupplierShouldFailAndWriteReport(Tas
195197 void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport (
196198 Task task ) throws IOException {
197199 prepareTask (task , "objects/requireNonNullWithSupplier" );
198- build (this .gradleBuild .withProhibitObjectsRequireNonNull (task , false ), task );
200+ build (this .gradleBuild .withProhibitObjectsRequireNonNull (false ), task );
199201 }
200202
201203 @ ParameterizedTest (name = "{0}" )
@@ -295,6 +297,25 @@ void whenEnumSourceValueIsSameAsTypeOfMethodsFirstParameterShouldFailAndWriteRep
295297 "should not have a value that is the same as the type of the method's first parameter" );
296298 }
297299
300+ @ Test
301+ void whenConditionalOnClassUsedOnBeanMethodsWithMainSourcesShouldFailAndWriteReport () throws IOException {
302+ prepareTask (Task .CHECK_ARCHITECTURE_MAIN , "conditionalonclass" , "annotations" );
303+ GradleBuild gradleBuild = this .gradleBuild .withDependencies (SPRING_CONTEXT )
304+ .withConditionalOnClassAnnotation (TestConditionalOnClass .class .getName ());
305+ buildAndFail (gradleBuild , Task .CHECK_ARCHITECTURE_MAIN ,
306+ "because @ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent"
307+ + " the method signature from being loaded. Such condition need to be placed"
308+ + " on a @Configuration class, allowing the condition to back off before the type is loaded" );
309+ }
310+
311+ @ Test
312+ void whenConditionalOnClassUsedOnBeanMethodsWithTestSourcesShouldSucceedAndWriteEmptyReport () throws IOException {
313+ prepareTask (Task .CHECK_ARCHITECTURE_TEST , "conditionalonclass" , "annotations" );
314+ GradleBuild gradleBuild = this .gradleBuild .withDependencies (SPRING_CONTEXT )
315+ .withConditionalOnClassAnnotation (TestConditionalOnClass .class .getName ());
316+ build (gradleBuild , Task .CHECK_ARCHITECTURE_TEST );
317+ }
318+
298319 private void prepareTask (Task task , String ... sourceDirectories ) throws IOException {
299320 for (String sourceDirectory : sourceDirectories ) {
300321 FileSystemUtils .copyRecursively (
@@ -310,7 +331,7 @@ private void prepareTask(Task task, String... sourceDirectories) throws IOExcept
310331 private void build (GradleBuild gradleBuild , Task task ) throws IOException {
311332 try {
312333 BuildResult buildResult = gradleBuild .build (task .toString ());
313- assertThat (buildResult .taskPaths (TaskOutcome .SUCCESS )).contains (":" + task );
334+ assertThat (buildResult .taskPaths (TaskOutcome .SUCCESS )).as ( buildResult . getOutput ()). contains (":" + task );
314335 assertThat (task .getFailureReport (gradleBuild .getProjectDir ())).isEmpty ();
315336 }
316337 catch (UnexpectedBuildFailure ex ) {
@@ -326,7 +347,7 @@ private void build(GradleBuild gradleBuild, Task task) throws IOException {
326347 private void buildAndFail (GradleBuild gradleBuild , Task task , String ... messages ) throws IOException {
327348 try {
328349 BuildResult buildResult = gradleBuild .buildAndFail (task .toString ());
329- assertThat (buildResult .taskPaths (TaskOutcome .FAILED )).contains (":" + task );
350+ assertThat (buildResult .taskPaths (TaskOutcome .FAILED )).as ( buildResult . getOutput ()). contains (":" + task );
330351 assertThat (task .getFailureReport (gradleBuild .getProjectDir ())).contains (messages );
331352 }
332353 catch (UnexpectedBuildSuccess ex ) {
@@ -371,7 +392,7 @@ private static final class GradleBuild {
371392
372393 private final Set <String > dependencies = new LinkedHashSet <>();
373394
374- private final Map <Task , Boolean > prohibitObjectsRequireNonNull = new LinkedHashMap <>();
395+ private final Map <Task , TaskConfiguration > taskConfigurations = new LinkedHashMap <>();
375396
376397 private GradleBuild (Path projectDir ) {
377398 this .projectDir = projectDir ;
@@ -381,12 +402,28 @@ Path getProjectDir() {
381402 return this .projectDir ;
382403 }
383404
384- GradleBuild withProhibitObjectsRequireNonNull (Task task , boolean prohibitObjectsRequireNonNull ) {
385- this .prohibitObjectsRequireNonNull .put (task , prohibitObjectsRequireNonNull );
405+ GradleBuild withProhibitObjectsRequireNonNull (Boolean prohibitObjectsRequireNonNull ) {
406+ for (Task task : Task .values ()) {
407+ configureTask (task , (configuration ) -> configuration
408+ .withProhibitObjectsRequireNonNull (prohibitObjectsRequireNonNull ));
409+ }
410+ return this ;
411+ }
412+
413+ GradleBuild withConditionalOnClassAnnotation (String annotationName ) {
414+ for (Task task : Task .values ()) {
415+ configureTask (task , (configuration ) -> configuration .withConditionalOnClassAnnotation (annotationName ));
416+ }
386417 return this ;
387418 }
388419
420+ private void configureTask (Task task , UnaryOperator <TaskConfiguration > configurer ) {
421+ this .taskConfigurations .computeIfAbsent (task , (key ) -> new TaskConfiguration (null , null ));
422+ this .taskConfigurations .compute (task , (key , value ) -> configurer .apply (value ));
423+ }
424+
389425 GradleBuild withDependencies (String ... dependencies ) {
426+ this .dependencies .clear ();
390427 this .dependencies .addAll (Arrays .asList (dependencies ));
391428 return this ;
392429 }
@@ -415,22 +452,40 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
415452 if (!this .dependencies .isEmpty ()) {
416453 buildFile .append ("dependencies {\n " );
417454 for (String dependency : this .dependencies ) {
418- buildFile .append (" implementation '%s' \n " . formatted (dependency ));
455+ buildFile .append ("\n implementation " ). append ( StringUtils . quote (dependency ));
419456 }
420457 buildFile .append ("}\n " );
421458 }
422- this .prohibitObjectsRequireNonNull .forEach ((task , prohibitObjectsRequireNonNull ) -> buildFile .append (task )
423- .append (" {\n " )
424- .append (" prohibitObjectsRequireNonNull = " )
425- .append (prohibitObjectsRequireNonNull )
426- .append ("\n }\n \n " ));
459+ this .taskConfigurations .forEach ((task , configuration ) -> {
460+ buildFile .append (task ).append (" {" );
461+ if (configuration .conditionalOnClassAnnotation () != null ) {
462+ buildFile .append ("\n conditionalOnClassAnnotation = " )
463+ .append (StringUtils .quote (configuration .conditionalOnClassAnnotation ()));
464+ }
465+ if (configuration .prohibitObjectsRequireNonNull () != null ) {
466+ buildFile .append ("\n prohibitObjectsRequireNonNull = " )
467+ .append (configuration .prohibitObjectsRequireNonNull ());
468+ }
469+ buildFile .append ("\n }\n " );
470+ });
427471 Files .writeString (this .projectDir .resolve ("build.gradle" ), buildFile , StandardCharsets .UTF_8 );
428472 return GradleRunner .create ()
429473 .withProjectDir (this .projectDir .toFile ())
430474 .withArguments (arguments )
431475 .withPluginClasspath ();
432476 }
433477
478+ private record TaskConfiguration (Boolean prohibitObjectsRequireNonNull , String conditionalOnClassAnnotation ) {
479+
480+ private TaskConfiguration withConditionalOnClassAnnotation (String annotationName ) {
481+ return new TaskConfiguration (this .prohibitObjectsRequireNonNull , annotationName );
482+ }
483+
484+ private TaskConfiguration withProhibitObjectsRequireNonNull (Boolean prohibitObjectsRequireNonNull ) {
485+ return new TaskConfiguration (prohibitObjectsRequireNonNull , this .conditionalOnClassAnnotation );
486+ }
487+ }
488+
434489 }
435490
436491}
0 commit comments