|
11 | 11 | package org.junit.platform.engine.support.hierarchical; |
12 | 12 |
|
13 | 13 | import static org.assertj.core.api.Assertions.assertThat; |
| 14 | +import static org.assertj.core.api.InstanceOfAssertFactories.LIST; |
| 15 | +import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; |
14 | 16 | import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; |
15 | 17 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; |
16 | 18 | import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; |
|
20 | 22 | import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; |
21 | 23 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; |
22 | 24 |
|
| 25 | +import java.util.LinkedHashSet; |
23 | 26 | import java.util.List; |
| 27 | +import java.util.Set; |
24 | 28 | import java.util.concurrent.locks.Lock; |
25 | 29 | import java.util.function.Function; |
26 | 30 |
|
| 31 | +import org.jspecify.annotations.NullMarked; |
27 | 32 | import org.junit.jupiter.api.Nested; |
28 | 33 | import org.junit.jupiter.api.Test; |
29 | 34 | import org.junit.jupiter.api.parallel.ResourceAccessMode; |
|
32 | 37 | import org.junit.jupiter.engine.JupiterTestEngine; |
33 | 38 | import org.junit.platform.engine.TestDescriptor; |
34 | 39 | import org.junit.platform.engine.UniqueId; |
| 40 | +import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; |
| 41 | +import org.junit.platform.engine.support.descriptor.EngineDescriptor; |
35 | 42 |
|
36 | 43 | /** |
37 | 44 | * @since 1.3 |
@@ -171,6 +178,55 @@ void coarsensGlobalLockToEngineDescriptorChild() { |
171 | 178 | assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); |
172 | 179 | } |
173 | 180 |
|
| 181 | + @Test |
| 182 | + void putsGlobalReadLockOnFirstNodeThatRequiresIt() { |
| 183 | + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("dummy"), "Dummy"); |
| 184 | + |
| 185 | + var containerWithoutBehavior = new NodeStub(engineDescriptor.getUniqueId().append("container", "1"), |
| 186 | + "Container 1") // |
| 187 | + .withGlobalReadLockRequired(false); |
| 188 | + var test1 = new NodeStub(containerWithoutBehavior.getUniqueId().append("test", "1"), "Test 1") // |
| 189 | + .withExclusiveResource(new ExclusiveResource("key1", READ_WRITE)); |
| 190 | + containerWithoutBehavior.addChild(test1); |
| 191 | + |
| 192 | + var containerWithBehavior = new NodeStub(engineDescriptor.getUniqueId().append("container", "2"), "Container 2") // |
| 193 | + .withGlobalReadLockRequired(true); |
| 194 | + var test2 = new NodeStub(containerWithBehavior.getUniqueId().append("test", "2"), "Test 2") // |
| 195 | + .withExclusiveResource(new ExclusiveResource("key2", READ_WRITE)); |
| 196 | + containerWithBehavior.addChild(test2); |
| 197 | + |
| 198 | + engineDescriptor.addChild(containerWithoutBehavior); |
| 199 | + engineDescriptor.addChild(containerWithBehavior); |
| 200 | + |
| 201 | + var advisor = nodeTreeWalker.walk(engineDescriptor); |
| 202 | + |
| 203 | + assertThat(advisor.getResourceLock(containerWithoutBehavior)) // |
| 204 | + .extracting(allLocks(), LIST) // |
| 205 | + .isEmpty(); |
| 206 | + assertThat(advisor.getResourceLock(test1)) // |
| 207 | + .extracting(allLocks(), LIST) // |
| 208 | + .containsExactly(getLock(GLOBAL_READ), getReadWriteLock("key1")); |
| 209 | + |
| 210 | + assertThat(advisor.getResourceLock(containerWithBehavior)) // |
| 211 | + .extracting(allLocks(), LIST) // |
| 212 | + .containsExactly(getLock(GLOBAL_READ)); |
| 213 | + assertThat(advisor.getResourceLock(test2)) // |
| 214 | + .extracting(allLocks(), LIST) // |
| 215 | + .containsExactly(getReadWriteLock("key2")); |
| 216 | + } |
| 217 | + |
| 218 | + @Test |
| 219 | + void doesNotAllowExclusiveResourcesWithoutRequiringGlobalReadLock() { |
| 220 | + var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("dummy"), "Dummy"); |
| 221 | + var invalidNode = new NodeStub(engineDescriptor.getUniqueId().append("container", "1"), "Container") // |
| 222 | + .withGlobalReadLockRequired(false) // |
| 223 | + .withExclusiveResource(new ExclusiveResource("key", READ_WRITE)); |
| 224 | + engineDescriptor.addChild(invalidNode); |
| 225 | + |
| 226 | + assertPreconditionViolationFor(() -> nodeTreeWalker.walk(engineDescriptor)) // |
| 227 | + .withMessage("Node requiring exclusive resources must also require global read lock: " + invalidNode); |
| 228 | + } |
| 229 | + |
174 | 230 | private static Function<org.junit.platform.engine.support.hierarchical.ResourceLock, List<Lock>> allLocks() { |
175 | 231 | return ResourceLockSupport::getLocks; |
176 | 232 | } |
@@ -262,4 +318,41 @@ static class TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase { |
262 | 318 | void test() { |
263 | 319 | } |
264 | 320 | } |
| 321 | + |
| 322 | + @NullMarked |
| 323 | + static class NodeStub extends AbstractTestDescriptor implements Node<EngineExecutionContext> { |
| 324 | + |
| 325 | + private final Set<ExclusiveResource> exclusiveResources = new LinkedHashSet<>(); |
| 326 | + private boolean globalReadLockRequired = true; |
| 327 | + |
| 328 | + NodeStub(UniqueId uniqueId, String displayName) { |
| 329 | + super(uniqueId, displayName); |
| 330 | + } |
| 331 | + |
| 332 | + NodeStub withExclusiveResource(ExclusiveResource exclusiveResource) { |
| 333 | + exclusiveResources.add(exclusiveResource); |
| 334 | + return this; |
| 335 | + } |
| 336 | + |
| 337 | + NodeStub withGlobalReadLockRequired(boolean globalReadLockRequired) { |
| 338 | + this.globalReadLockRequired = globalReadLockRequired; |
| 339 | + return this; |
| 340 | + } |
| 341 | + |
| 342 | + @Override |
| 343 | + public boolean isGlobalReadLockRequired() { |
| 344 | + return globalReadLockRequired; |
| 345 | + } |
| 346 | + |
| 347 | + @Override |
| 348 | + public Type getType() { |
| 349 | + throw new UnsupportedOperationException("should not be called"); |
| 350 | + } |
| 351 | + |
| 352 | + @Override |
| 353 | + public Set<ExclusiveResource> getExclusiveResources() { |
| 354 | + return exclusiveResources; |
| 355 | + } |
| 356 | + } |
| 357 | + |
265 | 358 | } |
0 commit comments