summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHÃ¥kon Hallingstad <hakon@oath.com>2018-02-06 13:38:19 +0100
committerGitHub <noreply@github.com>2018-02-06 13:38:19 +0100
commit96c103ab9e0a9e87eb04846a40ba7096898415fb (patch)
treec18022555c069e528f8a172831806a4a03c4f4e0
parent83d111445aec4901200ac61e2076f6f704b0d206 (diff)
parentba9385ce2c55e68adb5e16c57603f5e018fada35 (diff)
Merge pull request #4926 from vespa-engine/hakonhall/amend-program-output-parse-exceptions-with-command-and-output-snippet
Amend program output parse exceptions with command and output snippet
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandResult.java32
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TerminalImpl.java1
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/UnexpectedOutputException2.java10
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java32
6 files changed, 97 insertions, 3 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java
index b84bd2d8fef..9f7aaab2060 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/ChildProcessException.java
@@ -26,13 +26,26 @@ public abstract class ChildProcessException extends RuntimeException {
* @param possiblyHugeOutput The output of the command
*/
protected ChildProcessException(String problem, String commandLine, String possiblyHugeOutput) {
- super(makeSnippet(
+ super(makeSnippet(problem, commandLine, possiblyHugeOutput));
+ }
+
+ protected ChildProcessException(RuntimeException cause,
+ String problem,
+ String commandLine,
+ String possiblyHugeOutput) {
+ super(makeSnippet(problem, commandLine, possiblyHugeOutput), cause);
+ }
+
+ private static String makeSnippet(String problem,
+ String commandLine,
+ String possiblyHugeOutput) {
+ return makeSnippet(
problem,
commandLine,
possiblyHugeOutput,
MAX_OUTPUT_PREFIX,
MAX_OUTPUT_SUFFIX,
- MAX_OUTPUT_SLACK));
+ MAX_OUTPUT_SLACK);
}
// Package-private instead of private for testing.
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
index 6c4de7ac1e3..9a8ed19e0ac 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLine.java
@@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@@ -66,11 +67,16 @@ public class CommandLine {
public CommandLine add(String... arguments) { return add(Arrays.asList(arguments)); }
/** Add arguments to the command. The first argument in the first call to add() is the program. */
- public CommandLine add(List<String> arguments) {
+ public CommandLine add(Collection<String> arguments) {
this.arguments.addAll(arguments);
return this;
}
+ /** Add arguments by splitting arguments by space. */
+ public CommandLine addTokens(String arguments) {
+ return add(arguments.split(" "));
+ }
+
/**
* Execute a shell-like program in a child process:
* - the program is recorded and logged as modifying the system, but see executeSilently().
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandResult.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandResult.java
index 12f0d546b36..e0e84242f78 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandResult.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandResult.java
@@ -3,6 +3,7 @@
package com.yahoo.vespa.hosted.node.admin.task.util.process;
import java.util.List;
+import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -39,6 +40,7 @@ public class CommandResult {
return getOutputLinesStream().collect(Collectors.toList());
}
+ /** Returns the output lines as a stream, omitting trailing empty lines. */
public Stream<String> getOutputLinesStream() {
if (output.isEmpty()) {
// For some reason an empty string => one-element list.
@@ -50,6 +52,36 @@ public class CommandResult {
}
/**
+ * Map this CommandResult to an instance of type R.
+ *
+ * If a RuntimeException is thrown by the mapper, it is wrapped in an
+ * UnexpectedOutputException2 that includes a snippet of the output in the message.
+ *
+ * This method is intended to be used as part of the verification of the output.
+ */
+ public <R> R map(Function<CommandResult, R> mapper) {
+ try {
+ return mapper.apply(this);
+ } catch (RuntimeException e) {
+ throw new UnexpectedOutputException2(e, "Failed to map output", commandLine.toString(), output);
+ }
+ }
+
+ /**
+ * Map the output to an instance of type R according to mapper, wrapping any
+ * RuntimeException in UnexpectedOutputException2 w/output snippet. See map() for details.
+ */
+ public <R> R mapOutput(Function<String, R> mapper) { return map(result -> mapper.apply(result.getOutput())); }
+
+ /**
+ * Map each output line to an instance of type R according to mapper, wrapping any
+ * RuntimeException in UnexpectedOutputException2 w/output snippet. See map() for details.
+ */
+ public <R> List<R> mapEachLine(Function<String, R> mapper) {
+ return map(result -> result.getOutputLinesStream().map(mapper).collect(Collectors.toList()));
+ }
+
+ /**
* Convenience method for getting the CommandLine, whose execution resulted in
* this CommandResult instance.
*
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TerminalImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TerminalImpl.java
index 422584b8ccd..8ec0d267f0d 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TerminalImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/TerminalImpl.java
@@ -19,6 +19,7 @@ public class TerminalImpl implements Terminal {
this.processFactory = processFactory;
}
+ @Override
public CommandLine newCommandLine(TaskContext taskContext) {
return new CommandLine(taskContext, processFactory);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/UnexpectedOutputException2.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/UnexpectedOutputException2.java
index e786452c0ef..82fae1aa70e 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/UnexpectedOutputException2.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/process/UnexpectedOutputException2.java
@@ -13,4 +13,14 @@ public class UnexpectedOutputException2 extends ChildProcessException {
public UnexpectedOutputException2(String problem, String commandLine, String possiblyHugeOutput) {
super("output was not of the expected format: " + problem, commandLine, possiblyHugeOutput);
}
+
+ /**
+ * @param problem Problem description, e.g. "Output is not of the form ^NAME=VALUE$"
+ */
+ public UnexpectedOutputException2(RuntimeException cause,
+ String problem,
+ String commandLine,
+ String possiblyHugeOutput) {
+ super(cause, "output was not of the expected format: " + problem, commandLine, possiblyHugeOutput);
+ }
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java
index dd6d9c8226d..397380461d6 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/process/CommandLineTest.java
@@ -49,6 +49,9 @@ public class CommandLineTest {
List<CommandLine> commandLines = terminal.getTestProcessFactory().getMutableCommandLines();
assertEquals(1, commandLines.size());
assertTrue(commandLine == commandLines.get(0));
+
+ int lines = result.map(r -> r.getOutputLines().size());
+ assertEquals(2, lines);
}
@Test
@@ -112,4 +115,33 @@ public class CommandLineTest {
e.getMessage());
}
}
+
+ @Test
+ public void mapException() {
+ terminal.ignoreCommand("output");
+ CommandResult result = terminal.newCommandLine(context).add("program").execute();
+ IllegalArgumentException exception = new IllegalArgumentException("foo");
+ try {
+ result.mapOutput(output -> { throw exception; });
+ fail();
+ } catch (UnexpectedOutputException2 e) {
+ assertEquals("Command 'program 2>&1' output was not of the expected format: " +
+ "Failed to map output: stdout/stderr: 'output'", e.getMessage());
+ assertTrue(e.getCause() == exception);
+ }
+ }
+
+ @Test
+ public void testMapEachLine() {
+ assertEquals(
+ 1 + 2 + 3,
+ terminal.ignoreCommand("1\n2\n3\n")
+ .newCommandLine(context)
+ .add("foo")
+ .execute()
+ .mapEachLine(Integer::valueOf)
+ .stream()
+ .mapToInt(i -> i)
+ .sum());
+ }
} \ No newline at end of file