1818import com .mongodb .MongoException ;
1919import com .mongodb .internal .TimeoutContext ;
2020import com .mongodb .internal .TimeoutSettings ;
21+ import org .junit .jupiter .api .Disabled ;
2122import org .junit .jupiter .api .Test ;
2223
24+ import java .util .concurrent .atomic .AtomicInteger ;
2325import java .util .function .BiConsumer ;
2426import java .util .function .Consumer ;
2527import java .util .function .Supplier ;
2628
2729import static com .mongodb .assertions .Assertions .assertNotNull ;
2830import static com .mongodb .internal .async .AsyncRunnable .beginAsync ;
31+ import static org .junit .jupiter .api .Assertions .assertTrue ;
2932
3033abstract class AsyncFunctionsAbstractTest extends AsyncFunctionsTestBase {
3134 private static final TimeoutContext TIMEOUT_CONTEXT = new TimeoutContext (new TimeoutSettings (0 , 0 , 0 , 0L , 0 ));
@@ -724,7 +727,85 @@ void testTryCatchTestAndRethrow() {
724727 }
725728
726729 @ Test
730+ @ Disabled ("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop" )
731+ void testWhile () {
732+ assertBehavesSameVariations (10 , // TODO check expected variations
733+ () -> {
734+ int i = 0 ;
735+ while (i < 3 && plainTest (i )) {
736+ i ++;
737+ sync (i );
738+ }
739+ },
740+ (callback ) -> {
741+ final int [] i = new int [1 ];
742+ beginAsync ().loopWhile (() -> i [0 ] < 3 && plainTest (i [0 ]), (c2 ) -> {
743+ i [0 ]++;
744+ async (i [0 ], c2 );
745+ }).finish (callback );
746+ });
747+ }
748+
749+ @ Test
750+ @ Disabled ("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop" )
751+ void testWhile2 () {
752+ assertBehavesSameVariations (14 , // TODO check expected variations
753+ () -> {
754+ int i = 0 ;
755+ while (i < 3 && plainTest (i )) {
756+ i ++;
757+ sync (i );
758+ }
759+ sync (i + 100 );
760+ },
761+ (callback ) -> {
762+ final int [] i = new int [1 ];
763+ beginAsync ().thenRun (c -> {
764+ beginAsync ().loopWhile (() -> i [0 ] < 3 && plainTest (i [0 ]), (c2 ) -> {
765+ i [0 ]++;
766+ async (i [0 ], c2 );
767+ }).finish (c );
768+ }).thenRun (c -> {
769+ async (i [0 ] + 100 , c );
770+ }).finish (callback );
771+ });
772+ }
773+
774+ @ Test
775+ @ Disabled ("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop" )
727776 void testRetryLoop () {
777+ assertBehavesSameVariations (InvocationTracker .DEPTH_LIMIT * 2 + 1 ,
778+ () -> {
779+ while (true ) {
780+ try {
781+ sync (plainTest (0 ) ? 1 : 2 );
782+ } catch (RuntimeException e ) {
783+ if (e .getMessage ().equals ("exception-1" )) {
784+ continue ;
785+ }
786+ throw e ;
787+ }
788+ break ;
789+ }
790+ },
791+ (callback ) -> {
792+ final boolean [] shouldContinue = new boolean []{true };
793+ beginAsync ().loopWhile (() -> shouldContinue [0 ], (c ) -> {
794+ beginAsync ().thenRun (c2 -> {
795+ async (plainTest (0 ) ? 1 : 2 , c2 );
796+ }).thenRun (c2 -> {
797+ shouldContinue [0 ] = false ;
798+ c2 .complete (c2 );
799+ }).onErrorIf (e -> e .getMessage ().equals ("exception-1" ), (e , c2 ) -> {
800+ c2 .complete (c2 );
801+ }).finish (c );
802+ }).finish (callback );
803+ });
804+ }
805+
806+ @ Test
807+ void testThenRunRetryingWhile () {
808+ for (int i = 0 ; i < 1000 ; i ++) {
728809 assertBehavesSameVariations (InvocationTracker .DEPTH_LIMIT * 2 + 1 ,
729810 () -> {
730811 while (true ) {
@@ -746,10 +827,11 @@ void testRetryLoop() {
746827 e -> e .getMessage ().equals ("exception-1" )
747828 ).finish (callback );
748829 });
749- }
830+ }}
750831
751832 @ Test
752833 void testDoWhileLoop () {
834+ for (int i = 0 ; i < 1000 ; i ++) {
753835 assertBehavesSameVariations (67 ,
754836 () -> {
755837 do {
@@ -766,7 +848,7 @@ void testDoWhileLoop() {
766848 () -> plainTest (2 )
767849 ).finish (finalCallback );
768850 });
769- }
851+ }}
770852
771853 @ Test
772854 void testDoWhileLoop2 () {
@@ -1009,4 +1091,67 @@ void testDerivation() {
10091091 }).finish (callback );
10101092 });
10111093 }
1094+
1095+ @ Test
1096+ @ Disabled ("Tests AsyncRunnable.thenRun/finish, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop" )
1097+ void testStackDepthBounded () {
1098+ AtomicInteger maxDepth = new AtomicInteger (0 );
1099+ AtomicInteger minDepth = new AtomicInteger (Integer .MAX_VALUE );
1100+ AtomicInteger maxMongoDepth = new AtomicInteger (0 );
1101+ AtomicInteger minMongoDepth = new AtomicInteger (Integer .MAX_VALUE );
1102+ AtomicInteger stepCount = new AtomicInteger (0 );
1103+ // Capture one sample of mongodb package frames for printing
1104+ String [][] sampleMongoFrames = {null };
1105+
1106+ AsyncRunnable chain = beginAsync ();
1107+ for (int i = 0 ; i < 1000 ; i ++) {
1108+ chain = chain .thenRun (c -> {
1109+ stepCount .incrementAndGet ();
1110+ StackTraceElement [] stack = Thread .currentThread ().getStackTrace ();
1111+ int depth = stack .length ;
1112+ maxDepth .updateAndGet (current -> Math .max (current , depth ));
1113+ minDepth .updateAndGet (current -> Math .min (current , depth ));
1114+ int mongoFrames = 0 ;
1115+ for (StackTraceElement frame : stack ) {
1116+ if (frame .getClassName ().startsWith ("com.mongodb" )) {
1117+ mongoFrames ++;
1118+ }
1119+ }
1120+ int mf = mongoFrames ;
1121+ maxMongoDepth .updateAndGet (current -> Math .max (current , mf ));
1122+ minMongoDepth .updateAndGet (current -> Math .min (current , mf ));
1123+ // Capture first sample
1124+ if (sampleMongoFrames [0 ] == null ) {
1125+ String [] frames = new String [mf ];
1126+ int idx = 0 ;
1127+ for (StackTraceElement frame : stack ) {
1128+ if (frame .getClassName ().startsWith ("com.mongodb" )) {
1129+ frames [idx ++] = frame .getClassName () + "." + frame .getMethodName ()
1130+ + "(" + frame .getFileName () + ":" + frame .getLineNumber () + ")" ;
1131+ }
1132+ }
1133+ sampleMongoFrames [0 ] = frames ;
1134+ }
1135+ c .complete (c );
1136+ });
1137+ }
1138+
1139+ chain .finish ((v , e ) -> {
1140+ assertTrue (stepCount .get () == 1000 , "Expected 1000 steps, got " + stepCount .get ());
1141+ int depth = maxDepth .get ();
1142+ int mongoDepth = maxMongoDepth .get ();
1143+ String summary = "Stack depth: min=" + minDepth .get () + ", max=" + depth
1144+ + " | MongoDB frames: min=" + minMongoDepth .get () + ", max=" + mongoDepth ;
1145+ System .out .printf (summary + "%n" );
1146+ if (sampleMongoFrames [0 ] != null ) {
1147+ System .out .printf ("MongoDB stack frames (sample):%n" );
1148+ for (int i = 0 ; i < sampleMongoFrames [0 ].length ; i ++) {
1149+ System .out .printf (" " + (i + 1 ) + ". " + sampleMongoFrames [0 ][i ] + "%n" );
1150+ }
1151+ }
1152+ assertTrue (depth < 200 ,
1153+ "Stack depth too deep (min=" + minDepth .get () + ", max=" + depth
1154+ + "). Trampoline may not be working correctly." );
1155+ });
1156+ }
10121157}
0 commit comments