Uploaded image for project: 'jBPM'
  1. jBPM
  2. JBPM-10231

Non-idempotent unit tests in `jbpm-runtime-manager`

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • None
    • None
    • None
    • None
    • False
    • Hide

      None

      Show
      None
    • False
    • NEW
    • NEW
    • ---
    • ---

      1. The Problem
         
        Some unit tests are non-idempotent, as they pass in the first run but fail in the second run in the same environment. A fix is necessary since unit tests shall be self-contained, ensuring that the state of the system under test is consistent at the beginning of each test. In practice, fixing non-idempotent tests can help proactively avoid state pollution that results in test order dependency (which could cause problems under test selection , prioritization or parallelization).
         
      2. Reproduce
         
        Using the `NIOInspector` plugin that supports rerunning JUnit tests in the same environment. Use `EventHnadlingTest#testRunMultiEventProcessPerRequestRuntimeManager` as an example:
        ```
        cd jbpm-runtime-manager
        mvn edu.illinois:NIOInspector:rerun -Dtest=org.jbpm.runtime.manager.concurrent.EventHnadlingTest#testRunMultiEventProcessPerRequestRuntimeManager
        ```
         
      3. 3 Non-Idempotent Tests & Proposed Fix
        1. EventHnadlingTest.testRunMultiEventProcessPerRequestRuntimeManager
          Reason: With "Per Request" strategy, for every call to `getRuntimeEngine()`, a new instance will usually be delivered with brand new `KieSession`. However, the exception to this is when `getRuntimeEngine()` invoked within the same transaction from different places (which would be the case if `testRunMultiEventProcessPerRequestRuntimeManager` is re-executed). In that case, the manager caches the currently active instance. Therefore, in the second execution of the test, an error will be thrown since the retrieved (cached) `KieSession` is already closed by the previous test execution. To resolve this, the runtime engine manager shall be disposed to ensure that its `KieSession` is destroyed as well. 
           
          Error message of one of the tests in the repeated run:
          ```
          java.lang.IllegalStateException: Session/EntityManager is closed

      at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:375)

      at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:148)

      at org.hibernate.internal.SessionImpl.joinTransaction(SessionImpl.java:3633)

      at org.drools.persistence.jpa.JpaPersistenceContext.joinTransaction(JpaPersistenceContext.java:106)

      at org.drools.persistence.PersistableRunner$TransactionInterceptor.execute(PersistableRunner.java:603)

      at org.drools.persistence.PersistableRunner$TransactionInterceptor.execute(PersistableRunner.java:572)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.internalExecute(OptimisticLockRetryInterceptor.java:102)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.execute(OptimisticLockRetryInterceptor.java:83)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.execute(OptimisticLockRetryInterceptor.java:44)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.drools.persistence.jta.TransactionLockInterceptor.execute(TransactionLockInterceptor.java:73)

      at org.drools.persistence.jta.TransactionLockInterceptor.execute(TransactionLockInterceptor.java:45)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.internalExecute(ExecutionErrorHandlerInterceptor.java:66)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.execute(ExecutionErrorHandlerInterceptor.java:52)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.execute(ExecutionErrorHandlerInterceptor.java:29)

      at org.drools.persistence.PersistableRunner.execute(PersistableRunner.java:400)

      at org.drools.persistence.PersistableRunner.execute(PersistableRunner.java:68)

      at org.drools.core.runtime.InternalLocalRunner.execute(InternalLocalRunner.java:37)

      at org.drools.core.runtime.InternalLocalRunner.execute(InternalLocalRunner.java:41)

      at org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession.startProcess(CommandBasedStatefulKnowledgeSession.java:275)

      at org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession.startProcess(CommandBasedStatefulKnowledgeSession.java:259)

      at org.jbpm.runtime.manager.concurrent.EventHnadlingTest.testRunMultiEventProcessPerRequestRuntimeManager(EventHnadlingTest.java:86)
      ```
      Fix: Add the line `manager.disposeRuntimeEngine(runtime);` before closing the manager
       

        1. PerCaseRuntimeManagerTest#testMultipleProcessesInSingleCaseCompletedInSequence
          Reason: The "Per Case" strategy specifies that every process instance that belongs to same case will be bound to a single `ksession` for its entire life time.  Once started, whenever such operations are invoked, the manager will retrieve the same `ksession`. Similarly, `manager.disposeRuntimeEngine(runtime)` shall be called before `manager.close()` to ensure that the second test execution does not directly retrieve the already closed `ksession`.
           
          Error message in the repeated run:
          ```
          java.lang.IllegalStateException: Session/EntityManager is closed

      at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:375)

      at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:148)

      at org.hibernate.internal.SessionImpl.joinTransaction(SessionImpl.java:3633)

      at org.drools.persistence.jpa.JpaPersistenceContext.joinTransaction(JpaPersistenceContext.java:106)

      at org.drools.persistence.PersistableRunner$TransactionInterceptor.execute(PersistableRunner.java:603)

      at org.drools.persistence.PersistableRunner$TransactionInterceptor.execute(PersistableRunner.java:572)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.internalExecute(OptimisticLockRetryInterceptor.java:102)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.execute(OptimisticLockRetryInterceptor.java:83)

      at org.drools.persistence.jpa.OptimisticLockRetryInterceptor.execute(OptimisticLockRetryInterceptor.java:44)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.drools.persistence.jta.TransactionLockInterceptor.execute(TransactionLockInterceptor.java:73)

      at org.drools.persistence.jta.TransactionLockInterceptor.execute(TransactionLockInterceptor.java:45)

      at org.drools.core.command.impl.AbstractInterceptor.executeNext(AbstractInterceptor.java:39)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.internalExecute(ExecutionErrorHandlerInterceptor.java:66)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.execute(ExecutionErrorHandlerInterceptor.java:52)

      at org.jbpm.runtime.manager.impl.error.ExecutionErrorHandlerInterceptor.execute(ExecutionErrorHandlerInterceptor.java:29)

      at org.drools.persistence.PersistableRunner.execute(PersistableRunner.java:400)

      at org.drools.persistence.PersistableRunner.execute(PersistableRunner.java:68)

      at org.drools.core.runtime.InternalLocalRunner.execute(InternalLocalRunner.java:37)

      at org.drools.core.runtime.InternalLocalRunner.execute(InternalLocalRunner.java:41)

      at org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession.getIdentifier(CommandBasedStatefulKnowledgeSession.java:134)

      at org.jbpm.runtime.manager.impl.PerCaseRuntimeManagerTest.testMultipleProcessesInSingleCaseCompletedInSequence(PerCaseRuntimeManagerTest.java:349)
      ```
      Fix: Same as the previous test: add `manager.disposeRuntimeEngine(runtime);` before closing the manager
       
       

        1. PerRequestRuntimeManagerTest#testMultiplePerRequestManagerFromSingleThread
          Reason: This is actually a typo - the process instance `runtime2` is managed by `manager2`, not `manager`.
          The original code is
          ```
          manager.disposeRuntimeEngine(runtime1);
          manager.disposeRuntimeEngine(runtime2);
          ```
          It shall be changed to 
          ```
          manager.disposeRuntimeEngine(runtime1);
          manager2.disposeRuntimeEngine(runtime2);
          ```
           
          Error message of one of the tests in the repeated run:
          ```
          java.lang.IllegalStateException: RuntimeManager with id second is already active

      at org.jbpm.runtime.manager.impl.AbstractRuntimeManager.<init>(AbstractRuntimeManager.java:141)

      at org.jbpm.runtime.manager.impl.PerRequestRuntimeManager.<init>(PerRequestRuntimeManager.java:74)

      at org.jbpm.runtime.manager.impl.RuntimeManagerFactoryImpl.newPerRequestRuntimeManager(RuntimeManagerFactoryImpl.java:73)

      at org.jbpm.runtime.manager.impl.PerRequestRuntimeManagerTest.testMultiplePerRequestManagerFromSingleThread(PerRequestRuntimeManagerTest.java:543)

      at java.base/java.lang.reflect.Method.invoke(Method.java:568)
      ```
      Fix: As described above
       
       

      1. Verifying this change
        After the patch, running the tests repeatedly in the same environment will not lead to failures.

              kverlaen@redhat.com Kris Verlaenen
              kaiyaok2 Kaiyao Ke (Inactive)
              Kaiyao Ke (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: