ruthst00 commented on code in PR #6735:
URL: https://github.com/apache/jmeter/pull/6735#discussion_r4066315928


##########
src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java:
##########
@@ -959,18 +959,29 @@ private static void processAssertion(SampleResult result, 
Assertion assertion) {
 
     private static void runPostProcessors(List<? extends PostProcessor> 
extractors) {
         for (PostProcessor ex : extractors) {
-            TestBeanHelper.prepare((TestElement) ex);
-            ex.process();
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Running postprocessor: {}", 
((AbstractTestElement) ex).getName());
+                }
+                TestBeanHelper.prepare((TestElement) ex);
+                ex.process();
+            } catch (Exception | JMeterError e) {

Review Comment:
   `JMeterStopTestException`, `JMeterStopTestNowException`, and 
`JMeterStopThreadException` all extend `RuntimeException`. The new `catch 
(Exception | JMeterError e)` block will silently swallow these exceptions, 
completely breaking the "Stop Test", "Stop Test Now", and "Stop Thread" 
control-flow mechanisms when they are thrown from inside a pre- or 
post-processor.
   
   The existing code in `processSampler()` and `run()` carefully catches these 
three exception types *before* the generic `Exception` catch, precisely to 
handle them correctly. The new code does the opposite — it catches `Exception` 
first and logs it as an error, discarding the intent entirely.
   
   __Fix required:__ Re-throw these control-flow exceptions before logging the 
generic error:
   
   ```java
   } catch (JMeterStopTestException | JMeterStopTestNowException | 
JMeterStopThreadException e) {
       throw e; // preserve control-flow semantics
   } catch (Exception | JMeterError e) {
       log.error("Error processing PostProcessor: {}", ((AbstractTestElement) 
ex).getName(), e);
   }
   ```
   



##########
src/core/src/test/java/org/apache/jmeter/threads/TestJMeterThread.java:
##########
@@ -168,6 +172,114 @@ void testBug63490EndTestWhenDelayIsTooLongForScheduler() {
         assertTrue(duration <= maxDuration, "Test plan should not run for 
longer than duration");
     }
 
+    @Test
+    void testPostProcessorExceptionHandling() {
+        JMeterContextService.getContext().setVariables(new JMeterVariables());
+
+        HashTree testTree = new HashTree();
+        LoopController samplerController = new LoopController();
+        samplerController.setLoops(1);
+        samplerController.setContinueForever(false);
+        samplerController.setEnabled(true);
+
+        DummySampler dummySampler = createSampler();
+        DummyPostProcessor failingPostProcessor = new DummyPostProcessor(true);
+        DummyPostProcessor secondPostProcessor = new DummyPostProcessor(false);
+
+        HashTree samplerControllerTree = testTree.add(samplerController);
+        HashTree samplerTree = samplerControllerTree.add(dummySampler);
+        samplerTree.add(failingPostProcessor);
+        samplerTree.add(secondPostProcessor);
+
+        ThreadGroup threadGroup = new ThreadGroup();
+        threadGroup.setNumThreads(1);
+
+        JMeterThread jMeterThread = new JMeterThread(testTree, threadGroup, 
null);
+        jMeterThread.setThreadGroup(threadGroup);
+        jMeterThread.run();
+
+        assertTrue(dummySampler.isCalled(), "Sampler should be executed");
+        assertTrue(failingPostProcessor.isCalled(), "Failing post processor 
should be executed");
+        assertTrue(secondPostProcessor.isCalled(), "Second post processor 
should still be executed after exception in first");
+    }
+
+    @Test
+    void testPreProcessorExceptionHandling() {
+        JMeterContextService.getContext().setVariables(new JMeterVariables());
+
+        HashTree testTree = new HashTree();
+        LoopController samplerController = new LoopController();
+        samplerController.setLoops(1);
+        samplerController.setContinueForever(false);
+        samplerController.setEnabled(true);
+
+        DummySampler dummySampler = createSampler();
+        DummyPreProcessor failingPreProcessor = new DummyPreProcessor(true);
+        DummyPreProcessor secondPreProcessor = new DummyPreProcessor(false);
+
+        HashTree samplerControllerTree2 = testTree.add(samplerController);
+        HashTree samplerTree2 = samplerControllerTree2.add(dummySampler);
+        samplerTree2.add(failingPreProcessor);
+        samplerTree2.add(secondPreProcessor);
+
+        ThreadGroup threadGroup = new ThreadGroup();
+        threadGroup.setNumThreads(1);
+
+        JMeterThread jMeterThread = new JMeterThread(testTree, threadGroup, 
null);
+        jMeterThread.setThreadGroup(threadGroup);
+        jMeterThread.run();
+
+        assertTrue(failingPreProcessor.isCalled(), "Failing pre processor 
should be executed");
+        assertTrue(secondPreProcessor.isCalled(), "Second pre processor should 
still be executed after exception in first");
+        assertTrue(dummySampler.isCalled(), "Sampler should still be executed 
after preprocessor exception");
+    }
+
+    private static class DummyPostProcessor extends AbstractTestElement 
implements PostProcessor {
+        private static final long serialVersionUID = 1L;
+        private final boolean shouldThrow;
+        private boolean called = false;
+
+        public DummyPostProcessor(boolean shouldThrow) {
+            this.shouldThrow = shouldThrow;
+            setEnabled(true);
+        }
+
+        @Override
+        public void process() {
+            called = true;
+            if (shouldThrow) {
+                throw new RuntimeException("PostProcessor test exception");
+            }
+        }
+
+        public boolean isCalled() {
+            return called;
+        }
+    }
+
+    private static class DummyPreProcessor extends AbstractTestElement 
implements PreProcessor {
+        private static final long serialVersionUID = 1L;
+        private final boolean shouldThrow;
+        private boolean called = false;
+
+        public DummyPreProcessor(boolean shouldThrow) {
+            this.shouldThrow = shouldThrow;
+            setEnabled(true);
+        }
+
+        @Override
+        public void process() {
+            called = true;
+            if (shouldThrow) {
+                throw new RuntimeException("PreProcessor test exception");
+            }
+        }
+
+        public boolean isCalled() {
+            return called;
+        }
+    }
+

Review Comment:
   ### Test Issues
   
   1. __`DummySampler.sample()` change is a side-effect:__ The PR changes the 
existing `DummySampler.sample()` to return a non-null `SampleResult` 
(previously returned `null`). This is needed for the new tests to work (so 
post-processors are actually invoked), but it silently changes the behaviour of 
the existing `testBug63490EndTestWhenDelayIsTooLongForScheduler` test. That 
test asserts `assertFalse(dummySampler.isCalled())` — the sampler is never 
reached due to the timer, so the change doesn't break it, but it's a fragile 
coupling. The new `DummySampler` variant used in the new tests should be a 
separate inner class (or the existing one should be left unchanged and a new 
subclass created for the new tests).
   
   2. __Variable naming:__ `samplerControllerTree2` in 
`testPreProcessorExceptionHandling()` uses a `2` suffix that is a copy-paste 
artifact from the post-processor test. Since it's a local variable in its own 
method, it should just be named `samplerControllerTree`.
   
   3. __Missing import ordering:__ The two new imports (`PostProcessor`, 
`PreProcessor`) are inserted between `ThreadListener` and `Timer`, breaking the 
alphabetical ordering that the rest of the import block follows. They should be 
placed before `ThreadListener`.
   
   4. __No assertion on `LAST_SAMPLE_OK`:__ The tests verify that processors 
and samplers are called, but don't verify that `JMeterThread.LAST_SAMPLE_OK` is 
set correctly after a processor failure. Given that the change affects error 
handling in the sampling pipeline, this would strengthen the test coverage.
   



##########
src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java:
##########
@@ -959,18 +959,29 @@ private static void processAssertion(SampleResult result, 
Assertion assertion) {
 
     private static void runPostProcessors(List<? extends PostProcessor> 
extractors) {
         for (PostProcessor ex : extractors) {
-            TestBeanHelper.prepare((TestElement) ex);
-            ex.process();
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Running postprocessor: {}", 
((AbstractTestElement) ex).getName());
+                }
+                TestBeanHelper.prepare((TestElement) ex);
+                ex.process();
+            } catch (Exception | JMeterError e) {
+                log.error("Error processing PostProcessor: {}", 
((AbstractTestElement) ex).getName(), e);
+            }
         }
     }
 
     private static void runPreProcessors(List<? extends PreProcessor> 
preProcessors) {
         for (PreProcessor ex : preProcessors) {
-            if (log.isDebugEnabled()) {
-                log.debug("Running preprocessor: {}", ((AbstractTestElement) 
ex).getName());
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Running preprocessor: {}", 
((AbstractTestElement) ex).getName());
+                }
+                TestBeanHelper.prepare((TestElement) ex);
+                ex.process();
+            } catch (Exception | JMeterError e) {

Review Comment:
   Same comment as above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to