diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java index 9f98bfacbdca..c3fb18049274 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java @@ -23,6 +23,8 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Predicate; @@ -39,6 +41,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Sam Brannen + * @author Pavel Anisimov * @see OutputCaptureExtension * @see OutputCaptureRule */ @@ -48,6 +51,8 @@ class OutputCapture implements CapturedOutput { private AnsiOutputState ansiOutputState; + private final Map, String> filter2CachedCapturedStrings = new ConcurrentHashMap<>(); + /** * Push a new system capture session onto the stack. */ @@ -55,7 +60,7 @@ final void push() { if (this.systemCaptures.isEmpty()) { this.ansiOutputState = AnsiOutputState.saveAndDisable(); } - this.systemCaptures.addLast(new SystemCapture()); + this.systemCaptures.addLast(new SystemCapture(this)); } /** @@ -97,7 +102,7 @@ public String toString() { */ @Override public String getAll() { - return get((type) -> true); + return get(Type.filterAll); } /** @@ -106,7 +111,7 @@ public String getAll() { */ @Override public String getOut() { - return get(Type.OUT::equals); + return get(Type.filterOut); } /** @@ -115,7 +120,7 @@ public String getOut() { */ @Override public String getErr() { - return get(Type.ERR::equals); + return get(Type.filterErr); } /** @@ -128,15 +133,21 @@ void reset() { private String get(Predicate filter) { Assert.state(!this.systemCaptures.isEmpty(), "No system captures found. Please check your output capture registration."); + String cachedCapturedStrings = this.filter2CachedCapturedStrings.get(filter); + if (cachedCapturedStrings != null) { + return cachedCapturedStrings; + } StringBuilder builder = new StringBuilder(); for (SystemCapture systemCapture : this.systemCaptures) { systemCapture.append(builder, filter); } - return builder.toString(); + String capturedStrings = builder.toString(); + this.filter2CachedCapturedStrings.put(filter, capturedStrings); + return capturedStrings; } /** - * A capture session that captures {@link System#out System.out} and {@link System#out + * A capture session that captures {@link System#out System.out} and {@link System#err * System.err}. */ private static class SystemCapture { @@ -149,26 +160,34 @@ private static class SystemCapture { private final List capturedStrings = new ArrayList<>(); - SystemCapture() { + private final OutputCapture owner; + + SystemCapture(OutputCapture owner) { this.out = new PrintStreamCapture(System.out, this::captureOut); this.err = new PrintStreamCapture(System.err, this::captureErr); + this.owner = owner; System.setOut(this.out); System.setErr(this.err); } void release() { + this.owner.filter2CachedCapturedStrings.clear(); System.setOut(this.out.getParent()); System.setErr(this.err.getParent()); } private void captureOut(String string) { synchronized (this.monitor) { + this.owner.filter2CachedCapturedStrings.remove(Type.filterOut); + this.owner.filter2CachedCapturedStrings.remove(Type.filterAll); this.capturedStrings.add(new CapturedString(Type.OUT, string)); } } private void captureErr(String string) { synchronized (this.monitor) { + this.owner.filter2CachedCapturedStrings.remove(Type.filterErr); + this.owner.filter2CachedCapturedStrings.remove(Type.filterAll); this.capturedStrings.add(new CapturedString(Type.ERR, string)); } } @@ -185,6 +204,7 @@ void append(StringBuilder builder, Predicate filter) { void reset() { synchronized (this.monitor) { + this.owner.filter2CachedCapturedStrings.clear(); this.capturedStrings.clear(); } } @@ -278,7 +298,13 @@ public String toString() { */ private enum Type { - OUT, ERR + OUT, ERR; + + private static final Predicate filterOut = OUT::equals; + + private static final Predicate filterErr = ERR::equals; + + private static final Predicate filterAll = (type) -> true; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java index 95345c57ea0c..c5d79774bd2c 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.