mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-26 23:45:47 +00:00
Native tests
This commit is contained in:
@@ -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)
|
||||
|
@@ -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*/
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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()))
|
||||
|
@@ -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<>(
|
||||
|
@@ -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;
|
||||
}
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user