2525import org .junit .jupiter .api .Disabled ;
2626import org .junit .jupiter .api .Test ;
2727import org .openrewrite .*;
28+ import org .openrewrite .internal .InMemoryLargeSourceSet ;
29+ import org .openrewrite .marker .Markup ;
2830import org .openrewrite .marker .Markers ;
2931import org .openrewrite .config .CompositeRecipe ;
3032import org .openrewrite .config .Environment ;
@@ -413,6 +415,37 @@ void singleRpcRecipeNoBatch() {
413415 );
414416 }
415417
418+ /**
419+ * When an RPC batch visit throws an exception (e.g. a recipe visitor fails on the
420+ * remote side), the error must be visible: the source file should carry a Markup.Error
421+ * marker and the error should be recorded in the SourcesFileErrors data table.
422+ */
423+ @ Test
424+ void batchVisitExceptionProducesErrorMarker () {
425+ // given
426+ // Two consecutive same-RPC recipes are required for the batch path to kick in
427+ Recipe r1 = client .prepareRecipe ("org.openrewrite.rpc.RewriteRpcTest$ThrowingRpcRecipe" , Map .of ());
428+ Recipe r2 = client .prepareRecipe ("org.openrewrite.rpc.RewriteRpcTest$ThrowingRpcRecipe" , Map .of ());
429+
430+ var errors = new java .util .ArrayList <Throwable >();
431+ var ctx = new InMemoryExecutionContext (errors ::add );
432+ var source = PlainText .builder ().text ("hello" ).sourcePath (Path .of ("test.txt" )).build ();
433+
434+ // when
435+ RecipeRun run = new RecipeScheduler ().scheduleRun (
436+ new CompositeRecipe (List .of (r1 , r2 )),
437+ new InMemoryLargeSourceSet (List .of (source )), ctx , 1 , 1 );
438+
439+ // then
440+ assertThat (errors ).isNotEmpty ();
441+
442+ List <Result > results = run .getChangeset ().getAllResults ();
443+ assertThat (results ).isNotEmpty ();
444+ assertThat (results .getFirst ().getAfter ().getMarkers ().findFirst (Markup .Error .class ))
445+ .describedAs ("Source should carry Markup.Error so the failure is visible" )
446+ .isPresent ();
447+ }
448+
416449 @ Test
417450 void getCursor () {
418451 var parent = new Cursor (null , Cursor .ROOT_VALUE );
@@ -432,6 +465,29 @@ public PlainText visitText(PlainText text, Integer p) {
432465 }
433466 }
434467
468+ @ SuppressWarnings ("unused" )
469+ public static class ThrowingRpcRecipe extends Recipe {
470+ @ Override
471+ public String getDisplayName () {
472+ return "Throwing RPC recipe" ;
473+ }
474+
475+ @ Override
476+ public String getDescription () {
477+ return "Test recipe that throws during visit." ;
478+ }
479+
480+ @ Override
481+ public TreeVisitor <?, ExecutionContext > getVisitor () {
482+ return new PlainTextVisitor <>() {
483+ @ Override
484+ public PlainText visitText (PlainText text , ExecutionContext ctx ) {
485+ throw new RuntimeException ("boom from RPC" );
486+ }
487+ };
488+ }
489+ }
490+
435491 @ SuppressWarnings ("unused" )
436492 static class RecipeWithRecipeList extends Recipe {
437493 @ Override
0 commit comments