@@ -319,4 +319,147 @@ class SkippableException extends RuntimeException {
319319 assertEquals (1 , stepExecution .getSkipCount ());
320320 }
321321
322+ @ Test
323+ void testFilterCountAccuracyInConcurrentMode () throws Exception {
324+ // given
325+ int itemCount = 10 ;
326+ AtomicInteger readCounter = new AtomicInteger (0 );
327+
328+ ItemReader <Integer > reader = () -> {
329+ int current = readCounter .incrementAndGet ();
330+ return current <= itemCount ? current : null ;
331+ };
332+
333+ ItemProcessor <Integer , Integer > filteringProcessor = item -> null ;
334+
335+ ItemWriter <Integer > writer = chunk -> {
336+ };
337+
338+ JobRepository jobRepository = new ResourcelessJobRepository ();
339+ ChunkOrientedStep <Integer , Integer > step = new ChunkOrientedStep <>("step" , 100 , reader , writer , jobRepository );
340+ step .setItemProcessor (filteringProcessor );
341+ step .setTaskExecutor (new SimpleAsyncTaskExecutor ());
342+ step .afterPropertiesSet ();
343+
344+ JobInstance jobInstance = new JobInstance (1L , "job" );
345+ JobExecution jobExecution = new JobExecution (1L , jobInstance , new JobParameters ());
346+ StepExecution stepExecution = new StepExecution (1L , "step" , jobExecution );
347+
348+ // when
349+ step .execute (stepExecution );
350+
351+ // then
352+ assertEquals (itemCount , stepExecution .getFilterCount (), "Race condition detected! Expected " + itemCount
353+ + " filtered items, but got " + stepExecution .getFilterCount ());
354+ }
355+
356+ @ Test
357+ void testFilterCountAccuracyInSequentialMode () throws Exception {
358+ // given
359+ int itemCount = 10 ;
360+ AtomicInteger readCounter = new AtomicInteger (0 );
361+
362+ ItemReader <Integer > reader = () -> {
363+ int current = readCounter .incrementAndGet ();
364+ return current <= itemCount ? current : null ;
365+ };
366+
367+ ItemProcessor <Integer , Integer > filteringProcessor = item -> null ;
368+ ItemWriter <Integer > writer = chunk -> {
369+ };
370+
371+ JobRepository jobRepository = new ResourcelessJobRepository ();
372+ ChunkOrientedStep <Integer , Integer > step = new ChunkOrientedStep <>("step" , 100 , reader , writer , jobRepository );
373+ step .setItemProcessor (filteringProcessor );
374+ step .afterPropertiesSet ();
375+
376+ JobInstance jobInstance = new JobInstance (1L , "job" );
377+ JobExecution jobExecution = new JobExecution (1L , jobInstance , new JobParameters ());
378+ StepExecution stepExecution = new StepExecution (1L , "step" , jobExecution );
379+
380+ // when
381+ step .execute (stepExecution );
382+
383+ // then
384+ assertEquals (itemCount , stepExecution .getFilterCount (), "Sequential mode should have accurate filter count" );
385+ }
386+
387+ @ Test
388+ void testProcessSkipCountAccuracyInConcurrentMode () throws Exception {
389+ // given
390+ int itemCount = 10 ;
391+ AtomicInteger readCounter = new AtomicInteger (0 );
392+
393+ ItemReader <Integer > reader = () -> {
394+ int current = readCounter .incrementAndGet ();
395+ return current <= itemCount ? current : null ;
396+ };
397+
398+ ItemProcessor <Integer , Integer > failingProcessor = item -> {
399+ throw new RuntimeException ("Simulated processing failure" );
400+ };
401+
402+ ItemWriter <Integer > writer = chunk -> {
403+ };
404+
405+ JobRepository jobRepository = new ResourcelessJobRepository ();
406+ ChunkOrientedStep <Integer , Integer > step = new ChunkOrientedStep <>("step" , 100 , reader , writer , jobRepository );
407+ step .setItemProcessor (failingProcessor );
408+ step .setTaskExecutor (new SimpleAsyncTaskExecutor ());
409+ step .setFaultTolerant (true );
410+ step .setRetryPolicy (RetryPolicy .withMaxRetries (1 ));
411+ step .setSkipPolicy ((throwable , skipCount ) -> throwable instanceof RuntimeException );
412+
413+ step .afterPropertiesSet ();
414+
415+ JobInstance jobInstance = new JobInstance (1L , "job" );
416+ JobExecution jobExecution = new JobExecution (1L , jobInstance , new JobParameters ());
417+ StepExecution stepExecution = new StepExecution (1L , "step" , jobExecution );
418+
419+ // when
420+ step .execute (stepExecution );
421+
422+ // then
423+ assertEquals (itemCount , stepExecution .getProcessSkipCount (), "Race condition detected! Expected " + itemCount
424+ + " process skips, but got " + stepExecution .getProcessSkipCount ());
425+ }
426+
427+ @ Test
428+ void testProcessSkipCountAccuracyInSequentialMode () throws Exception {
429+ // given
430+ int itemCount = 10 ;
431+ AtomicInteger readCounter = new AtomicInteger (0 );
432+
433+ ItemReader <Integer > reader = () -> {
434+ int current = readCounter .incrementAndGet ();
435+ return current <= itemCount ? current : null ;
436+ };
437+
438+ ItemProcessor <Integer , Integer > failingProcessor = item -> {
439+ throw new RuntimeException ("Simulated processing failure" );
440+ };
441+
442+ ItemWriter <Integer > writer = chunk -> {
443+ };
444+
445+ JobRepository jobRepository = new ResourcelessJobRepository ();
446+ ChunkOrientedStep <Integer , Integer > step = new ChunkOrientedStep <>("step" , 100 , reader , writer , jobRepository );
447+ step .setItemProcessor (failingProcessor );
448+ step .setFaultTolerant (true );
449+ step .setRetryPolicy (RetryPolicy .withMaxRetries (1 ));
450+ step .setSkipPolicy ((throwable , skipCount ) -> throwable instanceof RuntimeException );
451+ step .afterPropertiesSet ();
452+
453+ JobInstance jobInstance = new JobInstance (1L , "job" );
454+ JobExecution jobExecution = new JobExecution (1L , jobInstance , new JobParameters ());
455+ StepExecution stepExecution = new StepExecution (1L , "step" , jobExecution );
456+
457+ // when
458+ step .execute (stepExecution );
459+
460+ // then
461+ assertEquals (itemCount , stepExecution .getProcessSkipCount (),
462+ "Sequential mode should have accurate process skip count" );
463+ }
464+
322465}
0 commit comments