Display command output asynchronously.
Replaced fail() function with failOnExit().
This commit is contained in:
parent
8b80ca1bc0
commit
e7d3060649
5 changed files with 35 additions and 170 deletions
25
README.md
25
README.md
|
@ -21,10 +21,9 @@ public void startServer() throws Exception {
|
|||
}
|
||||
```
|
||||
|
||||
### Failure Modes
|
||||
## Exit Status
|
||||
|
||||
Use the `fail` function to specify whether data returned to the standard streams and/or an abnormal exit value
|
||||
constitute a failure.
|
||||
Use the `failOnExit` function to specify whether a command non-zero exit status constitutes a failure.
|
||||
|
||||
```java
|
||||
@BuildCommand
|
||||
|
@ -38,28 +37,14 @@ public void startServer() throws Exception {
|
|||
new ExecOperation()
|
||||
.fromProject(this)
|
||||
.command(cmds)
|
||||
.fail(ExecFail.STDERR)
|
||||
.failOneExit(false)
|
||||
.execute();
|
||||
}
|
||||
```
|
||||
|
||||
The following predefined values are available:
|
||||
## Work Directory
|
||||
|
||||
| Name | Failure When |
|
||||
|:------------------|:-----------------------------------------------------------------|
|
||||
| `ExecFail.EXIT` | Exit value > 0 |
|
||||
| `ExecFail.NORMAL` | Exit value > 0 or any data to the standard error stream (stderr) |
|
||||
| `ExecFail.OUTPUT` | Any data to the standard output stream (stdout) or stderr. |
|
||||
| `ExecFail.STDERR` | Any data to stderr. |
|
||||
| `ExecFail.STDOUT` | Any data to stdout. |
|
||||
| `ExecFail.ALL` | Any of the conditions above. |
|
||||
| `ExecFail.NONE` | Never fails. |
|
||||
|
||||
`ExecFail.NORMAL` is the default value.
|
||||
|
||||
## Working Directory
|
||||
|
||||
You can also specify the working directory:
|
||||
You can also specify the work directory:
|
||||
|
||||
```java
|
||||
@BuildCommand
|
||||
|
|
|
@ -35,7 +35,7 @@ public class ExecOperationBuild extends Project {
|
|||
public ExecOperationBuild() {
|
||||
pkg = "rife.bld.extension";
|
||||
name = "ExecOperation";
|
||||
version = version(0, 9, 3);
|
||||
version = version(1, 0, 0);
|
||||
|
||||
javaRelease = 17;
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package rife.bld.extension;
|
||||
|
||||
/**
|
||||
* The failure modes enumeration.
|
||||
*
|
||||
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
|
||||
* @since 1.0
|
||||
*/
|
||||
public enum ExecFail {
|
||||
ALL, EXIT, NONE, NORMAL, OUTPUT, STDERR, STDOUT
|
||||
}
|
|
@ -21,8 +21,9 @@ import rife.bld.operations.AbstractOperation;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -36,7 +37,7 @@ import java.util.logging.Logger;
|
|||
public class ExecOperation extends AbstractOperation<ExecOperation> {
|
||||
private static final Logger LOGGER = Logger.getLogger(ExecOperation.class.getName());
|
||||
private final List<String> args_ = new ArrayList<>();
|
||||
private final Set<ExecFail> fail_ = new HashSet<>();
|
||||
private boolean failOnExit_ = true;
|
||||
private BaseProject project_;
|
||||
private int timeout = 30;
|
||||
private String workDir_;
|
||||
|
@ -80,8 +81,6 @@ public class ExecOperation extends AbstractOperation<ExecOperation> {
|
|||
LOGGER.severe("A project must be specified.");
|
||||
}
|
||||
|
||||
var errorMessage = new StringBuilder(27);
|
||||
|
||||
final File workDir;
|
||||
if (workDir_ == null || workDir_.isBlank()) {
|
||||
workDir = new File(project_.workDirectory().getAbsolutePath());
|
||||
|
@ -91,6 +90,7 @@ public class ExecOperation extends AbstractOperation<ExecOperation> {
|
|||
|
||||
if (workDir.isDirectory()) {
|
||||
var pb = new ProcessBuilder();
|
||||
pb.inheritIO();
|
||||
pb.command(args_);
|
||||
pb.directory(workDir);
|
||||
|
||||
|
@ -100,63 +100,28 @@ public class ExecOperation extends AbstractOperation<ExecOperation> {
|
|||
|
||||
var proc = pb.start();
|
||||
var err = proc.waitFor(timeout, TimeUnit.SECONDS);
|
||||
var stdout = readStream(proc.getInputStream());
|
||||
var stderr = readStream(proc.getErrorStream());
|
||||
|
||||
if (!err) {
|
||||
errorMessage.append("TIMEOUT");
|
||||
} else if (!fail_.contains(ExecFail.NONE)) {
|
||||
var all = fail_.contains(ExecFail.ALL);
|
||||
var output = fail_.contains(ExecFail.OUTPUT);
|
||||
if ((all || fail_.contains(ExecFail.EXIT) || fail_.contains(ExecFail.NORMAL)) && proc.exitValue() > 0) {
|
||||
errorMessage.append("EXIT ").append(proc.exitValue());
|
||||
if (!stderr.isEmpty()) {
|
||||
errorMessage.append(", STDERR -> ").append(stderr.get(0));
|
||||
} else if (!stdout.isEmpty()) {
|
||||
errorMessage.append(", STDOUT -> ").append(stdout.get(0));
|
||||
}
|
||||
} else if ((all || output || fail_.contains(ExecFail.STDERR) || fail_.contains(ExecFail.NORMAL))
|
||||
&& !stderr.isEmpty()) {
|
||||
errorMessage.append("STDERR -> ").append(stderr.get(0));
|
||||
} else if ((all || output || fail_.contains(ExecFail.STDOUT)) && !stdout.isEmpty()) {
|
||||
errorMessage.append("STDOUT -> ").append(stdout.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (LOGGER.isLoggable(Level.INFO) && errorMessage.isEmpty() && !stdout.isEmpty()) {
|
||||
for (var l : stdout) {
|
||||
LOGGER.info(l);
|
||||
}
|
||||
proc.destroy();
|
||||
throw new IOException("The command timed out.");
|
||||
} else if (proc.exitValue() != 0 && failOnExit_) {
|
||||
throw new IOException("The command exit status is: " + proc.exitValue());
|
||||
}
|
||||
} else {
|
||||
errorMessage.append("Invalid working directory: ").append(workDir.getCanonicalPath());
|
||||
}
|
||||
|
||||
if (!errorMessage.isEmpty()) {
|
||||
throw new IOException(errorMessage.toString());
|
||||
throw new IOException("Invalid work directory: " + workDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the failure mode.
|
||||
* Configures whether the operation should fail if the command exit status is not 0.
|
||||
* <p>
|
||||
* The failure modes are:
|
||||
* <ul>
|
||||
* <li>{@link ExecFail#EXIT}<p>Exit value > 0</p></li>
|
||||
* <li>{@link ExecFail#NORMAL}<p>Exit value > 0 or any data to the standard error stream (stderr)</p></li>
|
||||
* <li>{@link ExecFail#OUTPUT}<p>Any data to the standard output stream (stdout) or stderr</p></li>
|
||||
* <li>{@link ExecFail#STDERR}<p>Any data to stderr</p></li>
|
||||
* <li>{@link ExecFail#STDOUT}<p>Any data to stdout</p></li>
|
||||
* <li>{@link ExecFail#ALL}<p>Any of the conditions above</p></li>
|
||||
* <li>{@link ExecFail#NONE}<p>Never fails</p></li>
|
||||
* </ul>
|
||||
* Default is {@code TRUE}
|
||||
*
|
||||
* @param fail one or more failure modes
|
||||
* @return this operation instance
|
||||
* @see ExecFail
|
||||
* @param failOnExit The fail on exit toggle
|
||||
* @return this operation instance.
|
||||
*/
|
||||
public ExecOperation fail(ExecFail... fail) {
|
||||
fail_.addAll(Set.of(fail));
|
||||
public ExecOperation failOnExit(boolean failOnExit) {
|
||||
failOnExit_ = failOnExit;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -171,16 +136,6 @@ public class ExecOperation extends AbstractOperation<ExecOperation> {
|
|||
return this;
|
||||
}
|
||||
|
||||
private List<String> readStream(InputStream stream) {
|
||||
var lines = new ArrayList<String>();
|
||||
try (var scanner = new Scanner(stream)) {
|
||||
while (scanner.hasNextLine()) {
|
||||
lines.add(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the command timeout.
|
||||
*
|
||||
|
@ -193,7 +148,7 @@ public class ExecOperation extends AbstractOperation<ExecOperation> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Configures the working directory.
|
||||
* Configures the work directory.
|
||||
*
|
||||
* @param dir the directory path
|
||||
* @return this operation instance
|
||||
|
|
|
@ -19,10 +19,8 @@ package rife.bld.extension;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import rife.bld.BaseProject;
|
||||
import rife.bld.Project;
|
||||
import rife.bld.WebProject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -31,18 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatCode;
|
|||
|
||||
class ExecOperationTest {
|
||||
private static final String FOO = "foo";
|
||||
private static final String HELLO = "Hello";
|
||||
|
||||
@Test
|
||||
void testAll() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new Project())
|
||||
.command("date")
|
||||
.fail(ExecFail.ALL)
|
||||
.execute()
|
||||
).isInstanceOf(IOException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCat() throws Exception {
|
||||
|
@ -52,22 +38,11 @@ class ExecOperationTest {
|
|||
.fromProject(new Project())
|
||||
.timeout(10)
|
||||
.command("touch", tmpFile.getName())
|
||||
.fail(ExecFail.NORMAL)
|
||||
.execute();
|
||||
|
||||
assertThat(tmpFile).exists();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommandList() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new BaseProject())
|
||||
.command(List.of("logger", "-s", HELLO))
|
||||
.fail(ExecFail.STDERR)
|
||||
.execute()).message().startsWith("STDERR -> ").endsWith(HELLO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testException() {
|
||||
assertThatCode(() ->
|
||||
|
@ -78,54 +53,32 @@ class ExecOperationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testExit() {
|
||||
void testExitStatus() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new BaseProject())
|
||||
.command("tail", FOO)
|
||||
.fail(ExecFail.EXIT)
|
||||
.execute()).message().startsWith("EXIT ");
|
||||
.command(List.of("cat", FOO))
|
||||
.execute()).message().contains("exit status");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNone() {
|
||||
void testFailOnExit() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new WebProject())
|
||||
.command("cat", FOO)
|
||||
.fail(ExecFail.NONE)
|
||||
.fromProject(new BaseProject())
|
||||
.command(List.of("cat", FOO))
|
||||
.failOnExit(false)
|
||||
.execute()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOutput() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new WebProject())
|
||||
.command("echo")
|
||||
.fail(ExecFail.OUTPUT)
|
||||
.execute()
|
||||
).message().isEqualTo("STDOUT -> ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStdErr() {
|
||||
void testTimeout() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new BaseProject())
|
||||
.command("logger", "-s", HELLO)
|
||||
.fail(ExecFail.STDERR)
|
||||
.execute()).message().startsWith("STDERR -> ").endsWith(HELLO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStdOut() {
|
||||
assertThatCode(() ->
|
||||
new ExecOperation()
|
||||
.fromProject(new BaseProject())
|
||||
.command("echo", HELLO)
|
||||
.fail(ExecFail.STDOUT)
|
||||
.execute()).message().isEqualTo("STDOUT -> Hello");
|
||||
.timeout(5)
|
||||
.command(List.of("sleep", "10"))
|
||||
.execute()).message().contains("timed out");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -135,7 +88,6 @@ class ExecOperationTest {
|
|||
.fromProject(new BaseProject())
|
||||
.command("echo")
|
||||
.workDir(FOO)
|
||||
.fail(ExecFail.NORMAL)
|
||||
.execute()).message().startsWith("Invalid working directory: ").endsWith(FOO);
|
||||
.execute()).message().startsWith("Invalid work directory: ").endsWith(FOO);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue