Native tests

This commit is contained in:
Peter Palaga
2020-06-04 11:37:38 +02:00
parent 4565698444
commit c4221f8125
19 changed files with 764 additions and 415 deletions

View File

@@ -28,7 +28,7 @@ public class MultiModuleTest {
ClientLayout layout;
@Test
void cleanInstall() throws IOException {
void cleanInstall() throws IOException, InterruptedException {
final Path[] helloFilePaths = {
layout.multiModuleProjectDirectory().resolve("hello/target/hello.txt"),
layout.multiModuleProjectDirectory().resolve("hi/target/hi.txt")
@@ -53,7 +53,7 @@ public class MultiModuleTest {
client.execute(output, "clean", "install", "-e").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.satisfiesAnyOf( /* Two orderings are possible */
messages -> Assertions.assertThat(messages)

View File

@@ -0,0 +1,64 @@
package org.jboss.fuse.mvnd.it;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.junit.MvndNativeTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.mockito.InOrder;
import org.mockito.Mockito;
@MvndNativeTest(projectDir = "src/test/projects/single-module")
public class SingleModuleNativeIT {
@Inject
Client client;
@Inject
ClientLayout layout;
@Test
void cleanInstall(TestInfo testInfo) throws IOException, InterruptedException {
final Path helloFilePath = layout.multiModuleProjectDirectory().resolve("target/hello.txt");
if (Files.exists(helloFilePath)) {
Files.delete(helloFilePath);
}
final Path installedJar = layout.getLocalMavenRepository().resolve("org/jboss/fuse/mvnd/test/single-module/single-module/0.0.1-SNAPSHOT/single-module-0.0.1-SNAPSHOT.jar");
Assertions.assertThat(installedJar).doesNotExist();
final ClientOutput o = Mockito.mock(ClientOutput.class);
client.execute(o, "clean", "install", "-e").assertSuccess();
final Properties props = MvndTestUtil.properties(layout.multiModuleProjectDirectory().resolve("pom.xml"));
final InOrder inOrder = Mockito.inOrder(o);
inOrder.verify(o).accept(Mockito.contains("Building single-module"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-clean-plugin") + ":clean"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":compile"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":testCompile"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-surefire-plugin") + ":test"));
inOrder.verify(o).accept(Mockito.contains(MvndTestUtil.plugin(props, "maven-install-plugin") + ":install"));
inOrder.verify(o).accept(Mockito.contains("SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
assertJVM(o, props);
/* The target/hello.txt is created by HelloTest */
Assertions.assertThat(helloFilePath).exists();
Assertions.assertThat(installedJar).exists();
}
protected void assertJVM(ClientOutput o, Properties props) {
/* implemented in the subclass*/
}
}

View File

@@ -1,59 +1,16 @@
package org.jboss.fuse.mvnd.it;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.fuse.mvnd.assertj.MatchInOrderAmongOthers;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.junit.MvndTest;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@MvndTest(projectDir = "src/test/projects/single-module")
public class SingleModuleTest {
@Inject
Client client;
@Inject
ClientLayout layout;
@Test
void cleanInstall() throws IOException {
final Path helloFilePath = layout.multiModuleProjectDirectory().resolve("target/hello.txt");
if (Files.exists(helloFilePath)) {
Files.delete(helloFilePath);
}
final Path installedJar = layout.getLocalMavenRepository().resolve("org/jboss/fuse/mvnd/test/single-module/single-module/0.0.1-SNAPSHOT/single-module-0.0.1-SNAPSHOT.jar");
Assertions.assertThat(installedJar).doesNotExist();
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "clean", "install", "-e").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(
"Building single-module",
"maven-clean-plugin:[^:]+:clean",
"maven-compiler-plugin:[^:]+:compile",
"maven-compiler-plugin:[^:]+:testCompile",
"maven-surefire-plugin:[^:]+:test",
"maven-install-plugin:[^:]+:install",
"SUCCESS build of project org.jboss.fuse.mvnd.test.single-module:single-module"));
final Properties props = MvndTestUtil.properties(layout.multiModuleProjectDirectory().resolve("pom.xml"));
public class SingleModuleTest extends SingleModuleNativeIT {
protected void assertJVM(ClientOutput output, Properties props) {
final InOrder inOrder = Mockito.inOrder(output);
inOrder.verify(output).projectStateChanged(
"single-module",
@@ -106,11 +63,6 @@ public class SingleModuleTest {
":single-module");
inOrder.verify(output).projectFinished("single-module");
/* The target/hello.txt is created by HelloTest */
Assertions.assertThat(helloFilePath).exists();
Assertions.assertThat(installedJar).exists();
}
}

View File

@@ -27,7 +27,7 @@ public class StopStatusTest {
DaemonRegistry registry;
@Test
void stopStatus() throws IOException {
void stopStatus() throws IOException, InterruptedException {
/* The registry should be empty before we run anything */
Assertions.assertThat(registry.getAll()).isEmpty();
@@ -42,7 +42,7 @@ public class StopStatusTest {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "--status").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(
d.getUid() + " +" + d.getPid() + " +" + d.getAddress()));
@@ -73,7 +73,7 @@ public class StopStatusTest {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "--status").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(
logMessage.getAllValues().stream()
.filter(m -> m.contains(d.getUid()))

View File

@@ -24,13 +24,13 @@ public class VersionTest {
ClientLayout layout;
@Test
void version() throws IOException {
void version() throws IOException, InterruptedException {
final ClientOutput output = Mockito.mock(ClientOutput.class);
client.execute(output, "-v").assertSuccess();
final ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
Mockito.verify(output, Mockito.atLeast(1)).log(logMessage.capture());
Mockito.verify(output, Mockito.atLeast(1)).accept(logMessage.capture());
Assertions.assertThat(logMessage.getAllValues())
.is(new MatchInOrderAmongOthers<>(

View File

@@ -0,0 +1,26 @@
package org.jboss.fuse.mvnd.junit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jboss.fuse.mvnd.client.Client;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MvndTestExtension.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MvndNativeTest {
/**
* The path to the root directory of a test project relative to the current maven module directory. E.g.
* <code>@MvndTest(projectDir = "src/test/projects/my-project")</code>
*/
String projectDir();
/**
* Timeout for {@link Client#execute(org.jboss.fuse.mvnd.client.ClientOutput, java.util.List)} in seconds
*/
long timeoutSec() default 30;
}

View File

@@ -9,9 +9,9 @@ import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.jboss.fuse.mvnd.client.DefaultClient;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.DaemonInfo;
@@ -38,8 +38,14 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
final Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
final Class<?> testClass = context.getRequiredTestClass();
final MvndTest mnvdTest = testClass.getAnnotation(MvndTest.class);
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mnvdTest.projectDir()));
if (mnvdTest != null) {
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mnvdTest.projectDir(), false, -1L));
} else {
final MvndNativeTest mvndNativeTest = testClass.getAnnotation(MvndNativeTest.class);
store.put(MvndResource.class.getName(),
MvndResource.create(context.getRequiredTestClass().getSimpleName(), mvndNativeTest.projectDir(), true, mvndNativeTest.timeoutSec() * 1000L));
}
} catch (Exception e) {
this.bootException = e;
}
@@ -67,7 +73,16 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
} else if (f.getType() == Layout.class) {
f.set(testInstance, resource.layout);
} else if (f.getType() == Client.class) {
f.set(testInstance, new Client(resource.layout));
if (resource.isNative) {
final Path mvndNativeExecutablePath = Paths.get(System.getProperty("mvnd.native.executable"));
if (!Files.isRegularFile(mvndNativeExecutablePath)) {
throw new IllegalStateException("mvnd executable does not exist: " + mvndNativeExecutablePath);
}
f.set(testInstance, new NativeTestClient(resource.layout, mvndNativeExecutablePath, resource.timeoutMs));
} else {
f.set(testInstance, new DefaultClient(resource.layout));
}
} else if (f.getType() == NativeTestClient.class) {
}
}
}
@@ -88,8 +103,10 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
private final ClientLayout layout;
private final DaemonRegistry registry;
private final boolean isNative;
private final long timeoutMs;
public static MvndResource create(String className, String rawProjectDir) throws IOException {
public static MvndResource create(String className, String rawProjectDir, boolean isNative, long timeoutMs) throws IOException {
if (rawProjectDir == null) {
throw new IllegalStateException("rawProjectDir of @MvndTest must be set");
}
@@ -134,7 +151,7 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
localMavenRepository, settingsPath);
final DaemonRegistry registry = new DaemonRegistry(layout.registry());
return new MvndResource(layout, registry);
return new MvndResource(layout, registry, isNative, timeoutMs);
}
static Path deleteDir(Path dir) {
@@ -171,10 +188,12 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
return settingsPath;
}
public MvndResource(ClientLayout layout, DaemonRegistry registry) {
public MvndResource(ClientLayout layout, DaemonRegistry registry, boolean isNative, long timeoutMs) {
super();
this.layout = layout;
this.registry = registry;
this.isNative = isNative;
this.timeoutMs = timeoutMs;
}
@Override

View File

@@ -0,0 +1,193 @@
package org.jboss.fuse.mvnd.junit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.client.ClientOutput;
import org.jboss.fuse.mvnd.client.ExecutionResult;
/**
* A wrapper around the native executable.
*/
public class NativeTestClient implements Client {
public static final int TIMEOUT_EXIT_CODE = Integer.MIN_VALUE + 42;
private final ClientLayout layout;
private final Path mvndNativeExecutablePath;
private final long timeoutMs;
public NativeTestClient(ClientLayout layout, Path mvndNativeExecutablePath, long timeoutMs) {
super();
this.layout = layout;
this.mvndNativeExecutablePath = mvndNativeExecutablePath;
this.timeoutMs = timeoutMs;
}
@Override
public ExecutionResult execute(ClientOutput output, List<String> args) throws InterruptedException {
final List<String> cmd = new ArrayList<String>(args.size() + 1);
cmd.add(mvndNativeExecutablePath.toString());
args.stream().forEach(cmd::add);
cmd.add("-Dmaven.repo.local=" + layout.getLocalMavenRepository().toString());
ProcessBuilder builder = new ProcessBuilder(cmd.toArray(new String[0]))
.directory(layout.userDir().toFile()) //
.redirectErrorStream(false);
Map<String, String> env = builder.environment();
env.put("MAVEN_HOME", System.getProperty("mvnd.home"));
env.put("JAVA_HOME", System.getProperty("java.home"));
final String cmdString = cmd.stream().collect(Collectors.joining(" "));
output.accept("Executing " + cmdString);
try (CommandProcess process = new CommandProcess(builder.start(), cmd, output)) {
return process.waitFor(timeoutMs);
} catch (IOException e) {
throw new RuntimeException("Could not execute: " + cmdString, e);
}
}
public static class Result implements ExecutionResult {
private final int exitCode;
private final List<String> args;
public Result(List<String> args, int exitCode) {
super();
this.args = new ArrayList<>(args);
this.exitCode = exitCode;
}
StringBuilder appendCommand(StringBuilder sb) {
sb.append("mvnd");
for (String arg : args) {
sb.append(" \"").append(arg).append('"');
}
return sb;
}
public Result assertFailure() {
if (exitCode == 0) {
throw new AssertionError(appendCommand(
new StringBuilder("mvnd returned ").append(exitCode).append(" instead of non-zero exit code: ")));
}
return this;
}
public Result assertSuccess() {
if (exitCode != 0) {
throw new AssertionError(appendCommand(new StringBuilder("mvnd returned ").append(exitCode).append(": ")));
}
return this;
}
public int getExitCode() {
return exitCode;
}
public boolean isSuccess() {
return exitCode == 0;
}
}
/**
* A simple wrapper over {@link Process} that manages its destroying and offers Java 8-like
* {@link #waitFor(long, TimeUnit, String[])} with timeout.
*/
static class CommandProcess implements AutoCloseable {
private final Process process;
private final Thread shutDownHook;
private final StreamGobbler stdOut;
private List<String> args;
public CommandProcess(Process process, List<String> args, Consumer<String> outputConsumer) {
super();
this.process = process;
this.args = args;
this.stdOut = new StreamGobbler(process.getInputStream(), outputConsumer);
stdOut.start();
this.shutDownHook = new Thread(new Runnable() {
@Override
public void run() {
stdOut.cancel();
CommandProcess.this.process.destroy();
}
});
Runtime.getRuntime().addShutdownHook(shutDownHook);
}
@Override
public void close() {
process.destroy();
}
public ExecutionResult waitFor(long timeoutMs) throws InterruptedException, IOException {
final long deadline = System.currentTimeMillis() + timeoutMs;
final boolean timeouted = !process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
timeoutMs = Math.max(0, deadline - System.currentTimeMillis());
stdOut.join(timeoutMs);
stdOut.assertSuccess();
try {
Runtime.getRuntime().removeShutdownHook(shutDownHook);
} catch (Exception ignored) {
}
return new Result(args, timeouted ? TIMEOUT_EXIT_CODE : process.exitValue());
}
}
/**
* The usual friend of {@link Process#getInputStream()} / {@link Process#getErrorStream()}.
*/
static class StreamGobbler extends Thread {
private volatile boolean cancelled;
private IOException exception;
private final InputStream in;
private final Consumer<String> out;
private StreamGobbler(InputStream in, Consumer<String> out) {
this.in = in;
this.out = out;
}
public void assertSuccess() throws IOException {
if (exception != null) {
throw exception;
}
}
public void cancel() {
this.cancelled = true;
}
@Override
public void run() {
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
while (!cancelled && (line = r.readLine()) != null) {
out.accept(line);
}
} catch (IOException e) {
exception = e;
}
}
}
}