Skip to content

Commit 1789b0c

Browse files
fix: webtau step handles large amount of details without crash (#1651)
1 parent c0d5dea commit 1789b0c

6 files changed

Lines changed: 91 additions & 14 deletions

File tree

webtau-core/src/main/java/org/testingisdocumenting/webtau/reporter/TokenizedMessageToAnsiConverter.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private Stream<?> ansiSequenceFromPrettyPrinter(ValueConverter valueConverter, P
103103
printer.printObject(value);
104104
printer.flushCurrentLine();
105105

106-
Stream<Object> result = Stream.empty();
106+
Stream.Builder<Object> result = Stream.builder();
107107

108108
String indentation = StringUtils.createIndentation(currentLine.hasNewLine() ?
109109
currentLine.findWidthOfTheLastEffectiveLine():
@@ -118,24 +118,24 @@ private Stream<?> ansiSequenceFromPrettyPrinter(ValueConverter valueConverter, P
118118
boolean isLastLine = idx == printer.getNumberOfLines() - 1;
119119
PrettyPrinterLine line = printer.getLine(idx);
120120

121-
if (isFirstLine) {
122-
result = Stream.concat(result, line.getStyleAndValues().stream());
123-
} else {
124-
result = Stream.concat(result, Stream.concat(
125-
Stream.of(indentation),
126-
line.getStyleAndValues().stream()));
121+
if (!isFirstLine) {
122+
result.add(indentation);
127123
}
128124

125+
line.getStyleAndValues().forEach(result::add);
126+
129127
if (!isLastLine) {
130-
result = Stream.concat(result, Stream.of("\n"));
128+
result.add("\n");
131129
}
132130
}
133131

134132
if (numberOfLinesToPrint > 1 && numberOfLinesToPrint != printer.getNumberOfLines()) {
135-
result = Stream.concat(result, Stream.of(PrettyPrinter.DELIMITER_COLOR, indentation, "..."));
133+
result.add(PrettyPrinter.DELIMITER_COLOR);
134+
result.add(indentation);
135+
result.add("...");
136136
}
137137

138-
return result;
138+
return result.build();
139139
}
140140

141141
private void associateDefaultTokens() {

webtau-core/src/main/java/org/testingisdocumenting/webtau/reporter/WebTauStep.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,20 @@ public void setValueConverter(ValueConverter valueConverter) {
306306
}
307307

308308
public Stream<WebTauStepOutput> collectOutputs() {
309-
return Stream.concat(outputs.stream(), children.stream().flatMap(WebTauStep::collectOutputs));
309+
List<WebTauStepOutput> result = new ArrayList<>();
310+
Deque<WebTauStep> stack = new ArrayDeque<>();
311+
stack.push(this);
312+
313+
while (!stack.isEmpty()) {
314+
WebTauStep step = stack.pop();
315+
result.addAll(step.outputs);
316+
317+
for (int i = step.children.size() - 1; i >= 0; i--) {
318+
stack.push(step.children.get(i));
319+
}
320+
}
321+
322+
return result.stream();
310323
}
311324

312325
@SuppressWarnings("unchecked")
@@ -321,10 +334,22 @@ public boolean hasOutput(Class<? extends WebTauStepOutput> type) {
321334
}
322335

323336
public Stream<WebTauStep> stepsWithClassifier(String classifier) {
324-
Stream<WebTauStep> self = this.classifier.equals(classifier) ? Stream.of(this) : Stream.empty();
325-
Stream<WebTauStep> children = children().flatMap(childStep -> childStep.stepsWithClassifier(classifier));
337+
List<WebTauStep> result = new ArrayList<>();
338+
Deque<WebTauStep> stack = new ArrayDeque<>();
339+
stack.push(this);
340+
341+
while (!stack.isEmpty()) {
342+
WebTauStep step = stack.pop();
343+
if (step.classifier.equals(classifier)) {
344+
result.add(step);
345+
}
346+
347+
for (int i = step.children.size() - 1; i >= 0; i--) {
348+
stack.push(step.children.get(i));
349+
}
350+
}
326351

327-
return Stream.concat(self, children);
352+
return result.stream();
328353
}
329354

330355
public boolean hasFailedChildrenSteps() {

webtau-core/src/test/groovy/org/testingisdocumenting/webtau/reporter/TokenizedMessageToAnsiConverterTest.groovy

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,18 @@ class TokenizedMessageToAnsiConverterTest {
4141
def ansiSequence = new AutoResetAnsiString(valuesAndStyles.stream()).toString()
4242
actual(ansiSequence).should(equal("\u001B[1m\u001B[36mhello \u001B[0m\u001B[34mworld \u001B[1m\u001B[33mworld\u001B[0m"))
4343
}
44+
45+
@Test
46+
void "should handle large pretty printed value without stack overflow"() {
47+
def converter = TokenizedMessageToAnsiConverter.DEFAULT
48+
def largeList = (1..6000).collect { it }
49+
def message = new TokenizedMessage().value(largeList)
50+
51+
def valuesAndStyles = converter.convert(ValueConverter.EMPTY, message, 0)
52+
53+
def ansiString = new AutoResetAnsiString(valuesAndStyles.stream()).toString()
54+
def plainString = ansiString.replaceAll(/\u001B\[[;0-9]*m/, "")
55+
def expected = "[\n " + largeList.join(",\n ") + "\n]"
56+
actual(plainString).should(equal(expected))
57+
}
4458
}

webtau-core/src/test/groovy/org/testingisdocumenting/webtau/reporter/WebTauStepTest.groovy

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,36 @@ class WebTauStepTest {
269269
}
270270
}
271271

272+
@Test
273+
void "recursion methods should not cause stack overflow on deep trees"() {
274+
def root = createStep("root")
275+
def current = root
276+
def stepsToExecute = []
277+
278+
int depth = 2000
279+
depth.times { idx ->
280+
def child = WebTauStep.createStepWithExplicitParent(current, 0,
281+
tokenizedMessage().action("child #" + idx),
282+
{ Object ignored -> tokenizedMessage().action("done child #" + idx) },
283+
{ WebTauStepContext context -> return null })
284+
child.setClassifier("test")
285+
child.addOutput(new OutputA(id: "out" + idx))
286+
stepsToExecute.add(child)
287+
current = child
288+
}
289+
290+
stepsToExecute.reverseEach { it.execute(SKIP_ALL) }
291+
root.execute(SKIP_ALL)
292+
293+
def steps = root.stepsWithClassifier("test").collect(toList())
294+
assert steps.size() == depth
295+
assert steps.inProgressMessage*.toString() == (0..<depth).collect { "child #$it" }
296+
297+
def outputs = root.collectOutputs().collect(toList())
298+
assert outputs.size() == depth
299+
assert outputs*.toMap() == (0..<depth).collect { [id: "out$it"] }
300+
}
301+
272302
private static WebTauStep createStep(String title, Supplier stepCode = { return null }) {
273303
return WebTauStep.createStep(tokenizedMessage().action(title), {
274304
tokenizedMessage().action('done ' + title)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Fix: WebTau step now handles large amount of details without stack overflow error.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: 2026 Releases
3+
---
4+
5+
# 2.6
6+
7+
:include-markdowns: 2.6

0 commit comments

Comments
 (0)