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,11 +402,26 @@ 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 > customizer ) {
421+ this .taskConfigurations .computeIfAbsent (task , (key ) -> new TaskConfiguration (null , null ));
422+ this .taskConfigurations .compute (task , (key , value ) -> customizer .apply (value ));
423+ }
424+
389425 GradleBuild withDependencies (String ... dependencies ) {
390426 this .dependencies .addAll (Arrays .asList (dependencies ));
391427 return this ;
@@ -419,18 +455,36 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
419455 }
420456 buildFile .append ("}\n " );
421457 }
422- this .prohibitObjectsRequireNonNull .forEach ((task , prohibitObjectsRequireNonNull ) -> buildFile .append (task )
423- .append (" {\n " )
424- .append (" prohibitObjectsRequireNonNull = " )
425- .append (prohibitObjectsRequireNonNull )
426- .append ("\n }\n \n " ));
458+ this .taskConfigurations .forEach ((task , configuration ) -> {
459+ buildFile .append (task ).append (" {" );
460+ if (configuration .conditionalOnClassAnnotation () != null ) {
461+ buildFile .append ("\n conditionalOnClassAnnotation = " )
462+ .append (StringUtils .quote (configuration .conditionalOnClassAnnotation ()));
463+ }
464+ if (configuration .prohibitObjectsRequireNonNull () != null ) {
465+ buildFile .append ("\n prohibitObjectsRequireNonNull = " )
466+ .append (configuration .prohibitObjectsRequireNonNull ());
467+ }
468+ buildFile .append ("\n }\n " );
469+ });
427470 Files .writeString (this .projectDir .resolve ("build.gradle" ), buildFile , StandardCharsets .UTF_8 );
428471 return GradleRunner .create ()
429472 .withProjectDir (this .projectDir .toFile ())
430473 .withArguments (arguments )
431474 .withPluginClasspath ();
432475 }
433476
477+ private record TaskConfiguration (Boolean prohibitObjectsRequireNonNull , String conditionalOnClassAnnotation ) {
478+
479+ private TaskConfiguration withConditionalOnClassAnnotation (String annotationName ) {
480+ return new TaskConfiguration (this .prohibitObjectsRequireNonNull , annotationName );
481+ }
482+
483+ private TaskConfiguration withProhibitObjectsRequireNonNull (Boolean prohibitObjectsRequireNonNull ) {
484+ return new TaskConfiguration (prohibitObjectsRequireNonNull , this .conditionalOnClassAnnotation );
485+ }
486+ }
487+
434488 }
435489
436490}
0 commit comments