1717import java .util .ArrayList ;
1818import java .util .Collection ;
1919import java .util .List ;
20+ import java .util .Map ;
21+ import java .util .concurrent .ConcurrentHashMap ;
22+ import java .util .concurrent .atomic .AtomicReference ;
23+ import java .util .concurrent .locks .ReentrantLock ;
2024
2125import static java .util .Collections .emptyList ;
2226import static java .util .Collections .synchronizedList ;
2630import org .junit .platform .commons .PreconditionViolationException ;
2731import org .junit .platform .engine .Filter ;
2832import org .junit .platform .engine .TestExecutionResult ;
33+ import org .junit .platform .engine .UniqueId ;
2934import org .junit .platform .engine .discovery .DiscoverySelectors ;
3035import org .junit .platform .engine .support .descriptor .MethodSource ;
3136import org .junit .platform .launcher .Launcher ;
3540import org .junit .platform .launcher .core .LauncherDiscoveryRequestBuilder ;
3641import org .junit .platform .launcher .core .LauncherFactory ;
3742import org .pitest .testapi .Description ;
43+ import org .pitest .testapi .NullExecutionListener ;
3844import org .pitest .testapi .TestGroupConfig ;
3945import org .pitest .testapi .TestUnit ;
4046import org .pitest .testapi .TestUnitExecutionListener ;
@@ -97,10 +103,43 @@ private class TestIdentifierListener implements TestExecutionListener {
97103 private final Class <?> testClass ;
98104 private final TestUnitExecutionListener l ;
99105 private final List <TestIdentifier > identifiers = synchronizedList (new ArrayList <>());
106+ private final boolean serializeExecution ;
107+ // This map holds the locks that child tests of locked parent tests should use.
108+ // For example parallel data-driven Spock features start the feature execution which is CONTAINER_AND_TEST,
109+ // then wait for the parallel iteration executions to be finished which are TEST,
110+ // then finish the feature execution.
111+ // Due to that we cannot lock the iteration executions on the same lock as the feature executions,
112+ // as the feature execution is around all the subordinate iteration executions.
113+ //
114+ // This logic will of course break if there is some test engine that does strange setups like
115+ // having CONTAINER_AND_TEST with child CONTAINER that have child TEST and similar.
116+ // If those engines happen to be used, tests will start to deadlock, as the grand-child test
117+ // would not find the parent serializer and thus use the root serializer on which the grand-parent
118+ // CONTAINER_AND_TEST already locks.
119+ //
120+ // This setup would probably not make much sense, so should not be taken into account
121+ // unless such an engine actually pops up. If it does and someone tries to use it with PIT,
122+ // the logic should maybe be made more sophisticated like remembering the parent-child relationships
123+ // to be able to find the grand-parent serializer which is not possible stateless, because we are
124+ // only able to get the parent identifier directly, but not further up stateless.
125+ private final Map <UniqueId , AtomicReference <ReentrantLock >> parentCoverageSerializers = new ConcurrentHashMap <>();
126+ // This map holds the actual lock used for a specific test to be able to easily and safely unlock
127+ // without the need to recalculate which lock to use.
128+ private final Map <UniqueId , ReentrantLock > coverageSerializers = new ConcurrentHashMap <>();
129+ private final ReentrantLock rootCoverageSerializer = new ReentrantLock ();
100130
101131 public TestIdentifierListener (Class <?> testClass , TestUnitExecutionListener l ) {
102132 this .testClass = testClass ;
103133 this .l = l ;
134+ // PIT gives a coverage recording listener here during coverage recording
135+ // At the later stage during minion hunting a NullExecutionListener is given
136+ // as PIT is only interested in the resulting list of identifiers.
137+ // Serialization of test execution is only necessary during coverage calculation
138+ // currently. To be on the safe side serialize test execution for any listener
139+ // type except listener types where we know tests can run in parallel safely,
140+ // i.e. currently the NullExecutionListener which is the only other one besides
141+ // the coverage recording listener.
142+ serializeExecution = !(l instanceof NullExecutionListener );
104143 }
105144
106145 List <TestIdentifier > getIdentifiers () {
@@ -117,12 +156,32 @@ public void executionStarted(TestIdentifier testIdentifier) {
117156 && !includedTestMethods .contains (((MethodSource )testIdentifier .getSource ().get ()).getMethodName ())) {
118157 return ;
119158 }
120- l .executionStarted (new Description (testIdentifier .getUniqueId (), testClass ));
159+
160+ if (serializeExecution ) {
161+ coverageSerializers .compute (testIdentifier .getUniqueIdObject (), (uniqueId , lock ) -> {
162+ if (lock != null ) {
163+ throw new AssertionError ("No lock should be present" );
164+ }
165+
166+ // find the serializer to lock the test on
167+ // if there is a parent test locked, use the lock for its children if not,
168+ // use the root serializer
169+ return testIdentifier
170+ .getParentIdObject ()
171+ .map (parentCoverageSerializers ::get )
172+ .map (lockRef -> lockRef .updateAndGet (parentLock ->
173+ parentLock == null ? new ReentrantLock () : parentLock ))
174+ .orElse (rootCoverageSerializer );
175+ }).lock ();
176+ // record a potential serializer for child tests to lock on
177+ parentCoverageSerializers .put (testIdentifier .getUniqueIdObject (), new AtomicReference <>());
178+ }
179+
180+ l .executionStarted (new Description (testIdentifier .getUniqueId (), testClass ), true );
121181 identifiers .add (testIdentifier );
122182 }
123183 }
124184
125-
126185 @ Override
127186 public void executionFinished (TestIdentifier testIdentifier , TestExecutionResult testExecutionResult ) {
128187 // Classes with failing BeforeAlls never start execution and identify as 'containers' not 'tests'
@@ -134,6 +193,16 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult
134193 } else if (testIdentifier .isTest ()) {
135194 l .executionFinished (new Description (testIdentifier .getUniqueId (), testClass ), true );
136195 }
196+
197+ if (serializeExecution ) {
198+ // forget the potential serializer for child tests
199+ parentCoverageSerializers .remove (testIdentifier .getUniqueIdObject ());
200+ // unlock the serializer for the finished tests to let the next test continue
201+ ReentrantLock lock = coverageSerializers .remove (testIdentifier .getUniqueIdObject ());
202+ if (lock != null ) {
203+ lock .unlock ();
204+ }
205+ }
137206 }
138207
139208 }
0 commit comments