Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private Stream<?> ansiSequenceFromPrettyPrinter(ValueConverter valueConverter, P
printer.printObject(value);
printer.flushCurrentLine();

Stream<Object> result = Stream.empty();
Stream.Builder<Object> result = Stream.builder();

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

if (isFirstLine) {
result = Stream.concat(result, line.getStyleAndValues().stream());
} else {
result = Stream.concat(result, Stream.concat(
Stream.of(indentation),
line.getStyleAndValues().stream()));
if (!isFirstLine) {
result.add(indentation);
}

line.getStyleAndValues().forEach(result::add);

if (!isLastLine) {
result = Stream.concat(result, Stream.of("\n"));
result.add("\n");
}
}

if (numberOfLinesToPrint > 1 && numberOfLinesToPrint != printer.getNumberOfLines()) {
result = Stream.concat(result, Stream.of(PrettyPrinter.DELIMITER_COLOR, indentation, "..."));
result.add(PrettyPrinter.DELIMITER_COLOR);
result.add(indentation);
result.add("...");
}

return result;
return result.build();
}

private void associateDefaultTokens() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,20 @@ public void setValueConverter(ValueConverter valueConverter) {
}

public Stream<WebTauStepOutput> collectOutputs() {
return Stream.concat(outputs.stream(), children.stream().flatMap(WebTauStep::collectOutputs));
List<WebTauStepOutput> result = new ArrayList<>();
Deque<WebTauStep> stack = new ArrayDeque<>();
stack.push(this);

while (!stack.isEmpty()) {
WebTauStep step = stack.pop();
result.addAll(step.outputs);

for (int i = step.children.size() - 1; i >= 0; i--) {
stack.push(step.children.get(i));
}
}

return result.stream();
}

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

public Stream<WebTauStep> stepsWithClassifier(String classifier) {
Stream<WebTauStep> self = this.classifier.equals(classifier) ? Stream.of(this) : Stream.empty();
Stream<WebTauStep> children = children().flatMap(childStep -> childStep.stepsWithClassifier(classifier));
List<WebTauStep> result = new ArrayList<>();
Deque<WebTauStep> stack = new ArrayDeque<>();
stack.push(this);

while (!stack.isEmpty()) {
WebTauStep step = stack.pop();
if (step.classifier.equals(classifier)) {
result.add(step);
}

for (int i = step.children.size() - 1; i >= 0; i--) {
stack.push(step.children.get(i));
}
}

return Stream.concat(self, children);
return result.stream();
}

public boolean hasFailedChildrenSteps() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,18 @@ class TokenizedMessageToAnsiConverterTest {
def ansiSequence = new AutoResetAnsiString(valuesAndStyles.stream()).toString()
actual(ansiSequence).should(equal("\u001B[1m\u001B[36mhello \u001B[0m\u001B[34mworld \u001B[1m\u001B[33mworld\u001B[0m"))
}

@Test
void "should handle large pretty printed value without stack overflow"() {
def converter = TokenizedMessageToAnsiConverter.DEFAULT
def largeList = (1..6000).collect { it }
def message = new TokenizedMessage().value(largeList)

def valuesAndStyles = converter.convert(ValueConverter.EMPTY, message, 0)

def ansiString = new AutoResetAnsiString(valuesAndStyles.stream()).toString()
def plainString = ansiString.replaceAll(/\u001B\[[;0-9]*m/, "")
def expected = "[\n " + largeList.join(",\n ") + "\n]"
actual(plainString).should(equal(expected))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,31 @@ class WebTauStepTest {
}
}

@Test
void "recursion methods should not cause stack overflow on deep trees"() {
def root = createStep("root")
def current = root

int depth = 2000
depth.times { idx ->
def child = WebTauStep.createStepWithExplicitParent(current, 0,
tokenizedMessage().action("child #" + idx),
{ -> tokenizedMessage().action("done child #" + idx) },
{ -> })
child.setClassifier("test")
child.addOutput(new OutputA(id: "out" + idx))
current = child
}

def steps = root.stepsWithClassifier("test").collect(toList())
assert steps.size() == depth
assert steps.inProgressMessage*.toString() == (0..<depth).collect { "child #$it" }

def outputs = root.collectOutputs().collect(toList())
assert outputs.size() == depth
assert outputs*.toMap() == (0..<depth).collect { [id: "out$it"] }
}

private static WebTauStep createStep(String title, Supplier stepCode = { return null }) {
return WebTauStep.createStep(tokenizedMessage().action(title), {
tokenizedMessage().action('done ' + title)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fix: WebTau step now handles large amount of details without stack overflow error.
7 changes: 7 additions & 0 deletions webtau-docs/znai/release-notes/2026.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: 2026 Releases
---

# 2.6

:include-markdowns: 2.6
Loading