Skip to content

Commit a91ff37

Browse files
committed
Merge branch '3.4.x' into 3.5.x
Closes gh-46721
2 parents f41c12e + 932f9ed commit a91ff37

File tree

2 files changed

+83
-12
lines changed

2 files changed

+83
-12
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* @author Phillip Webb
4141
* @author Andy Wilkinson
4242
* @author Sam Brannen
43+
* @author Daniel Schmidt
4344
* @see OutputCaptureExtension
4445
* @see OutputCaptureRule
4546
*/
@@ -49,11 +50,15 @@ class OutputCapture implements CapturedOutput {
4950

5051
private AnsiOutputState ansiOutputState;
5152

52-
private final AtomicReference<String> out = new AtomicReference<>(null);
53+
private final AtomicReference<Object> out = new AtomicReference<>();
5354

54-
private final AtomicReference<String> err = new AtomicReference<>(null);
55+
private final AtomicReference<Object> err = new AtomicReference<>();
5556

56-
private final AtomicReference<String> all = new AtomicReference<>(null);
57+
private final AtomicReference<Object> all = new AtomicReference<>();
58+
59+
OutputCapture() {
60+
clearExisting();
61+
}
5762

5863
/**
5964
* Push a new system capture session onto the stack.
@@ -136,20 +141,21 @@ void reset() {
136141
}
137142

138143
void clearExisting() {
139-
this.out.set(null);
140-
this.err.set(null);
141-
this.all.set(null);
144+
this.out.set(new NoOutput());
145+
this.err.set(new NoOutput());
146+
this.all.set(new NoOutput());
142147
}
143148

144-
private String get(AtomicReference<String> existing, Predicate<Type> filter) {
149+
private String get(AtomicReference<Object> existing, Predicate<Type> filter) {
145150
Assert.state(!this.systemCaptures.isEmpty(),
146151
"No system captures found. Please check your output capture registration.");
147-
String result = existing.get();
148-
if (result == null) {
149-
result = build(filter);
150-
existing.compareAndSet(null, result);
152+
Object existingOutput = existing.get();
153+
if (existingOutput instanceof String) {
154+
return (String) existingOutput;
151155
}
152-
return result;
156+
String builtOutput = build(filter);
157+
existing.compareAndSet(existingOutput, builtOutput);
158+
return builtOutput;
153159
}
154160

155161
String build(Predicate<Type> filter) {
@@ -339,4 +345,8 @@ static AnsiOutputState saveAndDisable() {
339345

340346
}
341347

348+
static class NoOutput {
349+
350+
}
351+
342352
}

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/system/OutputCaptureTests.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.io.PrintStream;
2121
import java.util.NoSuchElementException;
22+
import java.util.concurrent.CountDownLatch;
23+
import java.util.concurrent.ExecutorService;
24+
import java.util.concurrent.Executors;
25+
import java.util.concurrent.Future;
26+
import java.util.concurrent.TimeUnit;
2227
import java.util.function.Predicate;
2328

2429
import org.junit.jupiter.api.AfterEach;
@@ -32,6 +37,7 @@
3237
* Tests for {@link OutputCapture}.
3338
*
3439
* @author Phillip Webb
40+
* @author Daniel Schmidt
3541
*/
3642
class OutputCaptureTests {
3743

@@ -188,6 +194,25 @@ void getErrUsesCache() {
188194
assertThat(this.output.buildCount).isEqualTo(2);
189195
}
190196

197+
@Test
198+
void getOutCacheShouldNotReturnStaleDataWhenDataIsLoggedWhileReading() throws Exception {
199+
TestLatchedOutputCapture output = new TestLatchedOutputCapture();
200+
output.push();
201+
System.out.print("A");
202+
ExecutorService executor = Executors.newFixedThreadPool(2);
203+
try {
204+
Future<?> reader = executor.submit(output::releaseAfterBuildAndAssertResultIsA);
205+
Future<?> writer = executor.submit(output::awaitReleaseAfterBuildThenWriteBAndRelease);
206+
reader.get();
207+
writer.get();
208+
}
209+
finally {
210+
executor.shutdown();
211+
executor.awaitTermination(10, TimeUnit.SECONDS);
212+
}
213+
assertThat(output.getOut()).isEqualTo("AB");
214+
}
215+
191216
private void pushAndPrint() {
192217
this.output.push();
193218
System.out.print("A");
@@ -220,4 +245,40 @@ String build(Predicate<Type> filter) {
220245

221246
}
222247

248+
static class TestLatchedOutputCapture extends OutputCapture {
249+
250+
private final CountDownLatch waitAfterBuild = new CountDownLatch(1);
251+
252+
private final CountDownLatch releaseAfterBuild = new CountDownLatch(1);
253+
254+
@Override
255+
String build(Predicate<Type> filter) {
256+
var result = super.build(filter);
257+
this.releaseAfterBuild.countDown();
258+
await(this.waitAfterBuild);
259+
return result;
260+
}
261+
262+
void releaseAfterBuildAndAssertResultIsA() {
263+
assertThat(getOut()).isEqualTo("A");
264+
}
265+
266+
void awaitReleaseAfterBuildThenWriteBAndRelease() {
267+
await(this.releaseAfterBuild);
268+
System.out.print("B");
269+
this.waitAfterBuild.countDown();
270+
}
271+
272+
private void await(CountDownLatch latch) {
273+
try {
274+
latch.await();
275+
}
276+
catch (InterruptedException ex) {
277+
Thread.currentThread().interrupt();
278+
throw new RuntimeException(ex);
279+
}
280+
}
281+
282+
}
283+
223284
}

0 commit comments

Comments
 (0)