Skip to content

Commit b233f68

Browse files
authored
test: test for console output in container environment (#21812)
Signed-off-by: Michael Heinrichs <netopyr@users.noreply.github.com>
1 parent 87f11e9 commit b233f68

File tree

3 files changed

+219
-36
lines changed

3 files changed

+219
-36
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package org.hiero.otter.fixtures.container.logging;
3+
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.hiero.otter.fixtures.logging.LogMessageParser.extractLogLevel;
6+
import static org.hiero.otter.fixtures.logging.LogMessageParser.isLogMessage;
7+
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.PrintStream;
10+
import java.nio.charset.StandardCharsets;
11+
import java.time.Duration;
12+
import org.apache.logging.log4j.LogManager;
13+
import org.apache.logging.log4j.core.LoggerContext;
14+
import org.hiero.otter.fixtures.Network;
15+
import org.hiero.otter.fixtures.TestEnvironment;
16+
import org.hiero.otter.fixtures.TimeManager;
17+
import org.hiero.otter.fixtures.container.ContainerTestEnvironment;
18+
import org.junit.jupiter.api.Test;
19+
20+
/**
21+
* Tests to verify console output in the Container-based test environment. This test validates the output on
22+
* the host machine that runs the test, not the output inside the containers.
23+
*/
24+
class ContainerConsoleOutputTest {
25+
26+
/**
27+
* Test basic console output capturing and log message verification.
28+
* The output should contain expected log messages from the Otter framework and user log statements.
29+
*/
30+
@Test
31+
void testBasicConsoleOutput() {
32+
// Capture console output
33+
final ByteArrayOutputStream consoleCapture = new ByteArrayOutputStream();
34+
final PrintStream originalOut = System.out;
35+
final PrintStream captureOut = new PrintStream(consoleCapture, true, StandardCharsets.UTF_8);
36+
37+
try {
38+
// Redirect System.out BEFORE creating the test environment
39+
System.setOut(captureOut);
40+
41+
// Force Log4j to reconfigure so ConsoleAppender picks up the new System.out
42+
final LoggerContext context = (LoggerContext) LogManager.getContext(false);
43+
context.reconfigure();
44+
45+
final TestEnvironment env = new ContainerTestEnvironment();
46+
try {
47+
final Network network = env.network();
48+
final TimeManager timeManager = env.timeManager();
49+
50+
// Start a 4-node network
51+
network.addNodes(4);
52+
network.start();
53+
54+
System.out.println("Hello Otter!");
55+
LogManager.getLogger().info("Hello Hiero!");
56+
LogManager.getLogger("com.acme.ExternalOtterTest").info("Hello World!");
57+
58+
// Wait 5 seconds
59+
timeManager.waitFor(Duration.ofSeconds(5L));
60+
61+
} finally {
62+
env.destroy();
63+
}
64+
65+
// Restore System.out before examining captured output
66+
System.setOut(originalOut);
67+
68+
// Get the captured console output
69+
final String consoleOutput = consoleCapture.toString(StandardCharsets.UTF_8);
70+
71+
// Verify that the console output contains expected log messages
72+
assertThat(consoleOutput)
73+
.as("Console output should contain 'Random seed:' entry")
74+
.doesNotContain("Random seed:"); // Turtle environment only
75+
assertThat(consoleOutput)
76+
.as("Console output should contain 'testcontainers' entry")
77+
.contains("testcontainers"); // Container environment only
78+
79+
// Verify presence of key log messages from Otter framework
80+
assertThat(consoleOutput)
81+
.as("Console output should contain 'Starting network...' message")
82+
.contains("Starting network...");
83+
assertThat(consoleOutput)
84+
.as("Console output should contain 'Network started.' message")
85+
.contains("Network started.");
86+
assertThat(consoleOutput)
87+
.as("Console output should contain 'Waiting for PT5S' message")
88+
.contains("Waiting for PT5S");
89+
assertThat(consoleOutput)
90+
.as("Console output should contain 'Destroying network...' message")
91+
.contains("Destroying network...");
92+
93+
// Verify presence of user log messages
94+
assertThat(consoleOutput)
95+
.as("Console output should contain 'Hello Otter!' message")
96+
.contains("Hello Otter!");
97+
assertThat(consoleOutput)
98+
.as("Console output should contain 'Hello Hiero!' message")
99+
.contains("Hello Hiero!");
100+
assertThat(consoleOutput)
101+
.as("Console output should contain 'Hello World!' message")
102+
.contains("Hello World!");
103+
104+
// Parse each line and verify log messages follow the expected pattern
105+
final String[] lines = consoleOutput.split("\n");
106+
for (final String line : lines) {
107+
if (line.trim().isEmpty()) {
108+
continue; // Skip empty lines
109+
}
110+
111+
// Check if this line is a log message (matches the log pattern)
112+
// Pattern: yyyy-MM-dd HH:mm:ss.SSS [thread] [optional marker] LEVEL logger - message
113+
// Example: 2025-10-22 16:29:13.345 [Test worker] INFO org.hiero.otter.fixtures... - ...
114+
if (isLogMessage(line)) {
115+
// Extract log level and logger name
116+
final String logLevel = extractLogLevel(line);
117+
118+
// Verify log level is INFO or more critical (not DEBUG or TRACE)
119+
assertThat(logLevel)
120+
.as("Log message should have INFO or higher level: %s", line)
121+
.isIn("INFO", "WARN", "ERROR", "FATAL");
122+
}
123+
}
124+
125+
} finally {
126+
// Ensure System.out is always restored even if an exception occurs
127+
System.setOut(originalOut);
128+
}
129+
}
130+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package org.hiero.otter.fixtures.logging;
3+
4+
import static java.util.Objects.requireNonNull;
5+
6+
import edu.umd.cs.findbugs.annotations.NonNull;
7+
import java.util.regex.Matcher;
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* Utility class for parsing log messages.
12+
*/
13+
public class LogMessageParser {
14+
15+
/**
16+
* Pattern for parsing log messages.
17+
* Matches: [thread] [optional marker] LEVEL logger - message
18+
* Group 1: marker (optional, e.g., STARTUP, PLATFORM_STATUS)
19+
* Group 2: log level (INFO, WARN, ERROR, etc.)
20+
* Group 3: logger name (e.g., org.hiero.otter.fixtures.internal.AbstractNetwork)
21+
*/
22+
private static final Pattern LOG_PATTERN =
23+
Pattern.compile("\\[.*?]\\s+(?:\\[([^\\]]+)])?\\s*(\\w+)\\s+(\\S+)\\s+-");
24+
25+
/**
26+
* Checks if a line matches the log message pattern.
27+
*
28+
* @param line the log line to check
29+
* @return {@code true} if the line matches the log message pattern, {@code false} otherwise
30+
* @throws NullPointerException if {@code line} is {@code null}
31+
*/
32+
public static boolean isLogMessage(@NonNull final String line) {
33+
requireNonNull(line);
34+
return LOG_PATTERN.matcher(line).find();
35+
}
36+
37+
/**
38+
* Extracts the marker from a log message line.
39+
* Returns empty string if the line doesn't match the expected pattern or if no marker is present.
40+
*
41+
* @param line the log line to extract the marker from
42+
* @return the extracted marker, or empty string if not found
43+
* @throws NullPointerException if {@code line} is {@code null}
44+
*/
45+
@NonNull
46+
public static String extractMarker(@NonNull final String line) {
47+
requireNonNull(line);
48+
final Matcher matcher = LOG_PATTERN.matcher(line);
49+
if (matcher.find()) {
50+
final String marker = matcher.group(1);
51+
return marker != null ? marker : "";
52+
}
53+
return "";
54+
}
55+
56+
/**
57+
* Extracts the log level from a log message line.
58+
* Returns empty string if the line doesn't match the expected pattern.
59+
*
60+
* @param line the log line to extract the log level from
61+
* @return the extracted log level, or empty string if not found
62+
* @throws NullPointerException if {@code line} is {@code null}
63+
*/
64+
@NonNull
65+
public static String extractLogLevel(@NonNull final String line) {
66+
requireNonNull(line);
67+
final Matcher matcher = LOG_PATTERN.matcher(line);
68+
return matcher.find() ? matcher.group(2) : "";
69+
}
70+
71+
/**
72+
* Extracts the logger name from a log message line.
73+
* Returns empty string if the line doesn't match the expected pattern.
74+
*
75+
* @param line the log line to extract the logger name from
76+
* @return the extracted logger name, or empty string if not found
77+
* @throws NullPointerException if {@code line} is {@code null}
78+
*/
79+
public static String extractLoggerName(final String line) {
80+
requireNonNull(line);
81+
final Matcher matcher = LOG_PATTERN.matcher(line);
82+
return matcher.find() ? matcher.group(3) : "";
83+
}
84+
}

platform-sdk/consensus-otter-tests/src/test/java/org/hiero/otter/fixtures/turtle/logging/TurtleConsoleOutputTest.java

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
package org.hiero.otter.fixtures.turtle.logging;
33

44
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.hiero.otter.fixtures.logging.LogMessageParser.extractLogLevel;
6+
import static org.hiero.otter.fixtures.logging.LogMessageParser.isLogMessage;
57

68
import java.io.ByteArrayOutputStream;
79
import java.io.PrintStream;
810
import java.nio.charset.StandardCharsets;
911
import java.time.Duration;
10-
import java.util.regex.Matcher;
11-
import java.util.regex.Pattern;
1212
import org.apache.logging.log4j.LogManager;
1313
import org.apache.logging.log4j.core.LoggerContext;
1414
import org.hiero.otter.fixtures.Network;
@@ -19,39 +19,6 @@
1919

2020
class TurtleConsoleOutputTest {
2121

22-
/**
23-
* Pattern for parsing log messages.
24-
* Matches: [thread] [optional marker] LEVEL logger - message
25-
* Group 1: log level (INFO, WARN, ERROR, etc.)
26-
* Group 2: logger name (e.g., org.hiero.otter.fixtures.internal.AbstractNetwork)
27-
*/
28-
private static final Pattern LOG_PATTERN = Pattern.compile("\\[.*?]\\s+(?:\\[.*?])?\\s*(\\w+)\\s+(\\S+)\\s+-");
29-
30-
/**
31-
* Checks if a line matches the log message pattern.
32-
*/
33-
private static boolean isLogMessage(final String line) {
34-
return LOG_PATTERN.matcher(line).find();
35-
}
36-
37-
/**
38-
* Extracts the log level from a log message line.
39-
* Returns empty string if the line doesn't match the expected pattern.
40-
*/
41-
private static String extractLogLevel(final String line) {
42-
final Matcher matcher = LOG_PATTERN.matcher(line);
43-
return matcher.find() ? matcher.group(1) : "";
44-
}
45-
46-
/**
47-
* Extracts the logger name from a log message line.
48-
* Returns empty string if the line doesn't match the expected pattern.
49-
*/
50-
private static String extractLoggerName(final String line) {
51-
final Matcher matcher = LOG_PATTERN.matcher(line);
52-
return matcher.find() ? matcher.group(2) : "";
53-
}
54-
5522
@Test
5623
void testBasicConsoleOutput() {
5724
// Capture console output
@@ -97,6 +64,9 @@ void testBasicConsoleOutput() {
9764
assertThat(consoleOutput)
9865
.as("Console output should contain 'Random seed:' entry")
9966
.contains("Random seed:");
67+
assertThat(consoleOutput)
68+
.as("Console output should contain 'Random seed:' entry")
69+
.doesNotContain("testcontainers"); // Container environment only
10070
assertThat(consoleOutput)
10171
.as("Console output should contain 'Starting network...' message")
10272
.contains("Starting network...");
@@ -132,7 +102,6 @@ void testBasicConsoleOutput() {
132102
if (isLogMessage(line)) {
133103
// Extract log level and logger name
134104
final String logLevel = extractLogLevel(line);
135-
final String loggerName = extractLoggerName(line);
136105

137106
// Verify log level is INFO or more critical (not DEBUG or TRACE)
138107
assertThat(logLevel)

0 commit comments

Comments
 (0)