diff options
author | HÃ¥kon Hallingstad <hakon@oath.com> | 2018-02-06 13:38:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-06 13:38:19 +0100 |
commit | 96c103ab9e0a9e87eb04846a40ba7096898415fb (patch) | |
tree | c18022555c069e528f8a172831806a4a03c4f4e0 | |
parent | 83d111445aec4901200ac61e2076f6f704b0d206 (diff) | |
parent | ba9385ce2c55e68adb5e16c57603f5e018fada35 (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
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 |