summaryrefslogtreecommitdiffstats
path: root/vespa-osgi-testrunner
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2022-07-07 11:45:24 +0200
committerjonmv <venstad@gmail.com>2022-07-07 11:45:24 +0200
commite5e4c8018da32ca5e7752c6bf1ccfc448ca8f2af (patch)
tree84bbad84e5b238dcc0e4ff3bb1ba8c80f40b462d /vespa-osgi-testrunner
parent4d01cadd925e5a72ff170fb5547f6645b8cbcf25 (diff)
Use jackson JsonGenerator for test output JSON
Diffstat (limited to 'vespa-osgi-testrunner')
-rw-r--r--vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestRunnerHandler.java164
-rw-r--r--vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/SampleTest.java2
-rw-r--r--vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/TestRunnerHandlerTest.java9
-rw-r--r--vespa-osgi-testrunner/src/test/resources/output.json46
-rw-r--r--vespa-osgi-testrunner/src/test/resources/report.json4
5 files changed, 104 insertions, 121 deletions
diff --git a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestRunnerHandler.java b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestRunnerHandler.java
index aa51971583b..ef4b402d33b 100644
--- a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestRunnerHandler.java
+++ b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestRunnerHandler.java
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.testrunner;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.yahoo.component.annotation.Inject;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.container.jdisc.EmptyResponse;
@@ -40,6 +42,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
public class TestRunnerHandler extends ThreadedHttpRequestHandler {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+ private static final JsonFactory factory = new JsonFactory();
private final TestRunner testRunner;
@@ -109,9 +112,10 @@ public class TestRunnerHandler extends ThreadedHttpRequestHandler {
return path.substring(lastSlash + 1);
}
- static void render(OutputStream out, Collection<LogRecord> log) throws IOException {
- out.write("{\"logRecords\":[".getBytes(UTF_8));
- boolean first = true;
+ private static void render(OutputStream out, Collection<LogRecord> log) throws IOException {
+ var json = factory.createGenerator(out);
+ json.writeStartObject();
+ json.writeArrayFieldStart("logRecords");
for (LogRecord record : log) {
String message = record.getMessage() == null ? "" : record.getMessage();
if (record.getThrown() != null) {
@@ -119,20 +123,19 @@ public class TestRunnerHandler extends ThreadedHttpRequestHandler {
record.getThrown().printStackTrace(new PrintStream(buffer));
message += (message.isEmpty() ? "" : "\n") + buffer;
}
-
- if (first) first = false;
- else out.write(',');
- out.write("""
- {"id":%d,"at":%d,"type":"%s","message":"%s"}
- """.formatted(record.getSequenceNumber(),
- record.getMillis(),
- typeOf(record.getLevel()),
- message).getBytes(UTF_8));
+ json.writeStartObject();
+ json.writeNumberField("id", record.getSequenceNumber());
+ json.writeNumberField("at", record.getMillis());
+ json.writeStringField("type", typeOf(record.getLevel()));
+ json.writeStringField("message", message);
+ json.writeEndObject();
}
- out.write("]}".getBytes(UTF_8));
+ json.writeEndArray();
+ json.writeEndObject();
+ json.close();
}
- public static String typeOf(Level level) {
+ private static String typeOf(Level level) {
return level.getName().equals("html") ? "html"
: level.intValue() < Level.INFO.intValue() ? "debug"
: level.intValue() < Level.WARNING.intValue() ? "info"
@@ -140,116 +143,105 @@ public class TestRunnerHandler extends ThreadedHttpRequestHandler {
: "error";
}
- static void render(OutputStream out, TestReport report) throws IOException {
- out.write('{');
+ private static void render(OutputStream out, TestReport report) throws IOException {
+ JsonGenerator json = factory.createGenerator(out);
+ json.writeStartObject();
- out.write("\"report\":".getBytes(UTF_8));
- render(out, (Node) report.root());
+ json.writeFieldName("report");
+ render(json, (Node) report.root());
// TODO jonmv: remove
- out.write(",\"summary\":{".getBytes(UTF_8));
+ json.writeObjectFieldStart("summary");
- renderSummary(out, report);
+ renderSummary(json, report);
- out.write(",\"failures\":[".getBytes(UTF_8));
- renderFailures(out, report.root(), true);
- out.write("]".getBytes(UTF_8));
+ json.writeArrayFieldStart("failures");
+ renderFailures(json, report.root());
+ json.writeEndArray();
- out.write("}".getBytes(UTF_8));
+ json.writeEndObject();
// TODO jonmv: remove
- out.write(",\"output\":[".getBytes(UTF_8));
- renderOutput(out, report.root(), true);
- out.write("]".getBytes(UTF_8));
+ json.writeArrayFieldStart("output");
+ renderOutput(json, report.root());
+ json.writeEndArray();
- out.write('}');
+ json.writeEndObject();
+ json.close();
}
- static void renderSummary(OutputStream out, TestReport report) throws IOException {
+ private static void renderSummary(JsonGenerator json, TestReport report) throws IOException {
Map<TestReport.Status, Long> tally = report.root().tally();
- out.write("""
- "success":%d,"failed":%d,"ignored":%d,"aborted":%d,"inconclusive":%d
- """.formatted(tally.getOrDefault(TestReport.Status.successful, 0L),
- tally.getOrDefault(TestReport.Status.failed, 0L) + tally.getOrDefault(TestReport.Status.error, 0L),
- tally.getOrDefault(TestReport.Status.skipped, 0L),
- tally.getOrDefault(TestReport.Status.aborted, 0L),
- tally.getOrDefault(TestReport.Status.inconclusive, 0L)).getBytes(UTF_8));
+ json.writeNumberField("success", tally.getOrDefault(TestReport.Status.successful, 0L));
+ json.writeNumberField("failed", tally.getOrDefault(TestReport.Status.failed, 0L) + tally.getOrDefault(TestReport.Status.error, 0L));
+ json.writeNumberField("ignored", tally.getOrDefault(TestReport.Status.skipped, 0L));
+ json.writeNumberField("aborted", tally.getOrDefault(TestReport.Status.aborted, 0L));
+ json.writeNumberField("inconclusive", tally.getOrDefault(TestReport.Status.inconclusive, 0L));
}
- static boolean renderFailures(OutputStream out, Node node, boolean first) throws IOException {
+ private static void renderFailures(JsonGenerator json, Node node) throws IOException {
if (node instanceof FailureNode) {
- if (first) first = false;
- else out.write(',');
- String message = ((FailureNode) node).thrown().getMessage();
- out.write("""
- {"testName":"%s","testError":%s,"exception":"%s"}
- """.formatted(node.parent.name(),
- message == null ? null : '"' + message + '"',
- ExceptionUtils.getStackTraceAsString(((FailureNode) node).thrown())).getBytes(UTF_8));
+ json.writeStartObject();
+ json.writeStringField("testName", node.parent.name());
+ json.writeStringField("testError", ((FailureNode) node).thrown().getMessage());
+ json.writeStringField("exception", ExceptionUtils.getStackTraceAsString(((FailureNode) node).thrown()));
+ json.writeEndObject();
}
else {
for (Node child : node.children())
- first = renderFailures(out, child, first);
+ renderFailures(json, child);
}
- return first;
}
- static boolean renderOutput(OutputStream out, Node node, boolean first) throws IOException {
+ private static void renderOutput(JsonGenerator json, Node node) throws IOException {
if (node instanceof OutputNode) {
for (LogRecord record : ((OutputNode) node).log())
- if (record.getMessage() != null) {
- if (first) first = false;
- else out.write(',');
- out.write(('"' + formatter.format(record.getInstant().atOffset(ZoneOffset.UTC)) + " " + record.getMessage() + '"').getBytes(UTF_8));
- }
+ if (record.getMessage() != null)
+ json.writeString(formatter.format(record.getInstant().atOffset(ZoneOffset.UTC)) + " " + record.getMessage());
}
else {
for (Node child : node.children())
- first = renderOutput(out, child, first);
+ renderOutput(json, child);
}
- return first;
}
- static void render(OutputStream out, Node node) throws IOException {
- out.write('{');
- if (node instanceof NamedNode) render(out, (NamedNode) node);
- if (node instanceof OutputNode) render(out, (OutputNode) node);
+ private static void render(JsonGenerator json, Node node) throws IOException {
+ json.writeStartObject();
+ if (node instanceof NamedNode) render(json, (NamedNode) node);
+ if (node instanceof OutputNode) render(json, (OutputNode) node);
if ( ! node.children().isEmpty()) {
- out.write(",\"children\":[".getBytes(UTF_8));
- boolean first = true;
+ json.writeArrayFieldStart("children");
for (Node child : node.children) {
- if (first) first = false;
- else out.write(',');
- render(out, child);
+ render(json, child);
}
- out.write(']');
+ json.writeEndArray();
}
- out.write('}');
+ json.writeEndObject();
}
- static void render(OutputStream out, NamedNode node) throws IOException {
+ private static void render(JsonGenerator json, NamedNode node) throws IOException {
String type = node instanceof FailureNode ? "failure" : node instanceof TestNode ? "test" : "container";
- out.write("""
- "type":"%s","name":"%s","status":"%s","start":%d,"duration":%d
- """.formatted(type,node.name(), node.status().name(), node.start().toEpochMilli(), node.duration().toMillis()).getBytes(UTF_8));
+ json.writeStringField("type", type);
+ json.writeStringField("name", node.name());
+ json.writeStringField("status", node.status().name());
+ json.writeNumberField("start", node.start().toEpochMilli());
+ json.writeNumberField("duration", node.duration().toMillis());
}
- static void render(OutputStream out, OutputNode node) throws IOException {
- out.write("\"type\":\"output\",\"children\":[".getBytes(UTF_8));
- boolean first = true;
+ private static void render(JsonGenerator json, OutputNode node) throws IOException {
+ json.writeStringField("type", "output");
+ json.writeArrayFieldStart("children");
for (LogRecord record : node.log()) {
- if (first) first = false;
- else out.write(',');
- out.write("""
- {"message":"%s","at":%d,"level":"%s"}
- """.formatted((record.getLoggerName() == null ? "" : record.getLoggerName() + ": ") +
- (record.getMessage() != null ? record.getMessage() : "") +
- (record.getThrown() != null ? (record.getMessage() != null ? "\n" : "") + traceToString(record.getThrown()) : ""),
- record.getInstant().toEpochMilli(),
- typeOf(record.getLevel())).getBytes(UTF_8));
+ json.writeStartObject();
+ json.writeStringField("message", (record.getLoggerName() == null ? "" : record.getLoggerName() + ": ") +
+ (record.getMessage() != null ? record.getMessage() : "") +
+ (record.getThrown() != null ? (record.getMessage() != null ? "\n" : "") + traceToString(record.getThrown()) : ""));
+ json.writeNumberField("at", record.getInstant().toEpochMilli());
+ json.writeStringField("level", typeOf(record.getLevel()));
+ json.writeEndObject();
}
- out.write(']');
+ json.writeEndArray();
}
private static String traceToString(Throwable thrown) {
@@ -258,17 +250,17 @@ public class TestRunnerHandler extends ThreadedHttpRequestHandler {
return buffer.toString(UTF_8);
}
- interface Renderer {
+ private interface Renderer {
void render(OutputStream out) throws IOException;
}
- static class CustomJsonResponse extends HttpResponse {
+ private static class CustomJsonResponse extends HttpResponse {
private final Renderer renderer;
- CustomJsonResponse(Renderer renderer) {
+ private CustomJsonResponse(Renderer renderer) {
super(200);
this.renderer = renderer;
}
diff --git a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/SampleTest.java b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/SampleTest.java
index bc878353d4b..b0e5119c06e 100644
--- a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/SampleTest.java
+++ b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/test/samples/SampleTest.java
@@ -71,7 +71,7 @@ public class SampleTest {
@Test
void successful() {
log.log(new Level("html", INFO.intValue()) { }, "<body />");
- log.log(INFO, "Very informative");
+ log.log(INFO, "Very informative: \"\\n\": \n");
log.log(WARNING, "Oh no", new IllegalArgumentException("error", new RuntimeException("wrapped")));
}
diff --git a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/TestRunnerHandlerTest.java b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/TestRunnerHandlerTest.java
index 5ce737d7649..6d6fbbf2cf1 100644
--- a/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/TestRunnerHandlerTest.java
+++ b/vespa-osgi-testrunner/src/test/java/com/yahoo/vespa/testrunner/TestRunnerHandlerTest.java
@@ -27,7 +27,6 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
-import java.util.logging.Level;
import java.util.logging.LogRecord;
import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
@@ -136,14 +135,6 @@ class TestRunnerHandlerTest {
}
}
- /* Creates a LogRecord that has a known instant and sequence number to get predictable serialization results. */
- private static LogRecord logRecord(String logMessage) {
- LogRecord logRecord = new LogRecord(Level.INFO, logMessage);
- logRecord.setInstant(testInstant);
- logRecord.setSequenceNumber(0);
- return logRecord;
- }
-
private static class MockRunner implements TestRunner {
private final TestRunner.Status status;
diff --git a/vespa-osgi-testrunner/src/test/resources/output.json b/vespa-osgi-testrunner/src/test/resources/output.json
index 847ae5800e9..2b4aa9e5599 100644
--- a/vespa-osgi-testrunner/src/test/resources/output.json
+++ b/vespa-osgi-testrunner/src/test/resources/output.json
@@ -1,133 +1,133 @@
{
"logRecords": [
{
- "id": 18,
+ "id": 2,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 21,
+ "id": 5,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 22,
+ "id": 6,
"at": 0,
"type": "error",
"message": "java.lang.NoClassDefFoundError\n\tat com.yahoo.vespa.test.samples.SampleTest.error(SampleTest.java:87)\n"
},
{
- "id": 25,
+ "id": 9,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 26,
+ "id": 10,
"at": 0,
"type": "info",
"message": "I have a bad feeling about this"
},
{
- "id": 27,
+ "id": 11,
"at": 0,
"type": "error",
"message": "org.opentest4j.AssertionFailedError: baz ==> expected: <foo> but was: <bar>\n\tat org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)\n\tat org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)\n\tat org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)\n\tat org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1152)\n\tat com.yahoo.vespa.test.samples.SampleTest.failing(SampleTest.java:81)\n"
},
{
- "id": 31,
+ "id": 15,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 32,
+ "id": 16,
"at": 0,
"type": "info",
"message": "I'm here with Erwin today; Erwin, what can you tell us about your cat?"
},
{
- "id": 33,
+ "id": 17,
"at": 0,
"type": "warning",
"message": "ai.vespa.hosted.cd.InconclusiveTestException: the cat is both dead _and_ alive\n\tat com.yahoo.vespa.test.samples.SampleTest.inconclusive(SampleTest.java:93)\n"
},
{
- "id": 36,
+ "id": 20,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 37,
+ "id": 21,
"at": 0,
"type": "info",
"message": "<body />"
},
{
- "id": 38,
+ "id": 22,
"at": 0,
"type": "info",
- "message": "Very informative"
+ "message": "Very informative: \"\\n\": \n"
},
{
- "id": 39,
+ "id": 23,
"at": 0,
"type": "warning",
"message": "Oh no\njava.lang.IllegalArgumentException: error\n\tat com.yahoo.vespa.test.samples.SampleTest.successful(SampleTest.java:75)\nCaused by: java.lang.RuntimeException: wrapped\n\t... 1 more\n"
},
{
- "id": 43,
+ "id": 27,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 46,
+ "id": 30,
"at": 0,
"type": "info",
"message": "Catch me if you can!"
},
{
- "id": 50,
+ "id": 34,
"at": 0,
"type": "error",
"message": "org.opentest4j.AssertionFailedError: no charm\n\tat org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39)\n\tat org.junit.jupiter.api.Assertions.fail(Assertions.java:134)\n\tat com.yahoo.vespa.test.samples.SampleTest$Inner.lambda$others$1(SampleTest.java:105)\n"
},
{
- "id": 54,
+ "id": 38,
"at": 0,
"type": "info",
"message": "spam"
},
{
- "id": 2,
+ "id": 67,
"at": 0,
"type": "error",
"message": "org.opentest4j.AssertionFailedError\n\tat org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:35)\n\tat org.junit.jupiter.api.Assertions.fail(Assertions.java:115)\n\tat com.yahoo.vespa.test.samples.FailingTestAndBothAftersTest.test(FailingTestAndBothAftersTest.java:19)\n\tSuppressed: java.lang.RuntimeException\n\t\tat com.yahoo.vespa.test.samples.FailingTestAndBothAftersTest.moreFail(FailingTestAndBothAftersTest.java:16)\n"
},
{
- "id": 4,
+ "id": 69,
"at": 0,
"type": "error",
"message": "java.lang.RuntimeException\n\tat com.yahoo.vespa.test.samples.FailingTestAndBothAftersTest.fail(FailingTestAndBothAftersTest.java:13)\n"
},
{
- "id": 7,
+ "id": 72,
"at": 0,
"type": "error",
"message": "org.junit.platform.commons.JUnitException: @BeforeAll method 'void com.yahoo.vespa.test.samples.WrongBeforeAllTest.wrong()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).\n"
},
{
- "id": 11,
+ "id": 76,
"at": 0,
"type": "error",
"message": "java.lang.NullPointerException\n\tat com.yahoo.vespa.test.samples.FailingExtensionTest$FailingExtension.<init>(FailingExtensionTest.java:19)\n"
},
{
- "id": 15,
+ "id": 80,
"at": 12000,
"type": "error",
"message": "java.lang.ClassNotFoundException: School's out all summer!\n"
diff --git a/vespa-osgi-testrunner/src/test/resources/report.json b/vespa-osgi-testrunner/src/test/resources/report.json
index fa76c222f93..443694e2e0c 100644
--- a/vespa-osgi-testrunner/src/test/resources/report.json
+++ b/vespa-osgi-testrunner/src/test/resources/report.json
@@ -191,7 +191,7 @@
"level": "info"
},
{
- "message": "com.yahoo.vespa.test.samples.SampleTest: Very informative",
+ "message": "com.yahoo.vespa.test.samples.SampleTest: Very informative: \"\\n\": \n",
"at": 0,
"level": "info"
},
@@ -546,7 +546,7 @@
"00:00:00.000 I'm here with Erwin today; Erwin, what can you tell us about your cat?",
"00:00:00.000 spam",
"00:00:00.000 <body />",
- "00:00:00.000 Very informative",
+ "00:00:00.000 Very informative: \"\\n\": \n",
"00:00:00.000 Oh no",
"00:00:00.000 spam",
"00:00:00.000 Catch me if you can!",