com.github.sdrss.testngstarter.mvnplugin.TestNGStarterMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testng-starter-maven-plugin Show documentation
Show all versions of testng-starter-maven-plugin Show documentation
A maven plugin to start testNG suites with all testNG/reportNG related attributes.
package com.github.sdrss.testngstarter.mvnplugin;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
import com.github.sdrss.testngstarter.mvnplugin.helper.LoaderFinder;
@Mojo(defaultPhase = LifecyclePhase.TEST, name = "test", requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST)
public class TestNGStarterMojo extends AbstractTestNGStarterMojo {
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Component
private DependencyResolver dependencyResolver;
@Component
private PluginDescriptor pluginDescriptor;
@Parameter(property = "skipTests", defaultValue = "false")
protected boolean skipTests;
private static final String TESTNGSTARTERMAINCLASS = "com.github.sdrss.testngstarter.mvnplugin.helper.TestNGStarterMainClass";
private static final String METHODNAME = "execute";
@Override
public void execute() throws MojoExecutionException {
Log logger = getLog();
if (verifyParameters(logger)) {
Properties properties = initProperties();
IsolatedThreadGroup isolatedThreadGroup = initThreadGroup();
Thread thread = initThread(isolatedThreadGroup, properties);
ClassLoader classLoader = getClassLoader();
thread.setContextClassLoader(classLoader);
thread.start();
joinNonDaemonThreads(isolatedThreadGroup);
if (true) {
terminateThreads(isolatedThreadGroup);
try {
isolatedThreadGroup.destroy();
} catch (IllegalThreadStateException e) {
getLog().warn("Couldn't destroy threadgroup " + isolatedThreadGroup, e);
}
}
synchronized (isolatedThreadGroup) {
if (isolatedThreadGroup.uncaughtException != null) {
throw new MojoExecutionException("An exception occured while executing the Java class. "
+ isolatedThreadGroup.uncaughtException.getMessage(), isolatedThreadGroup.uncaughtException);
}
}
}
}
private IsolatedThreadGroup initThreadGroup() {
return new IsolatedThreadGroup(TESTNGSTARTERMAINCLASS);
}
private Thread initThread(IsolatedThreadGroup threadGroup, Properties properties) {
return new Thread(threadGroup, new Runnable() {
public void run() {
try {
Class> bootClass = Thread.currentThread().getContextClassLoader().loadClass(TESTNGSTARTERMAINCLASS);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mainHandle = lookup.findStatic(bootClass, METHODNAME, MethodType.methodType(void.class, Properties.class));
mainHandle.invoke(properties);
} catch (IllegalAccessException e) { // just pass it on
Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), new Exception("The specified mainClass doesn't contain a main method with appropriate signature.", e));
} catch (InvocationTargetException e) { // use the cause if available to improve the plugin execution output
Throwable exceptionToReport = e.getCause() != null ? e.getCause() : e;
Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), exceptionToReport);
} catch (Throwable e) { // just pass it on
Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), e);
}
}
}, TESTNGSTARTERMAINCLASS + ".execute(properties)");
}
protected boolean verifyParameters(Log logger) {
if (isSkip()) {
logger.info("Tests are skipped.");
return false;
}
return true;
}
private boolean isSkip() {
return skipTests;
}
class IsolatedThreadGroup extends ThreadGroup {
private Throwable uncaughtException;
public IsolatedThreadGroup(String name) {
super(name);
}
public void uncaughtException(Thread thread, Throwable throwable) {
if (throwable instanceof ThreadDeath) {
return;
}
synchronized (this) {
if (uncaughtException == null) {
uncaughtException = throwable;
}
}
getLog().warn(throwable);
}
}
private ClassLoader getClassLoader() throws MojoExecutionException {
List classpathURLs = new ArrayList<>();
addRelevantProjectDependenciesToClasspath(classpathURLs);
try {
return LoaderFinder.find(classpathURLs, TESTNGSTARTERMAINCLASS);
} catch (NullPointerException | IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private void addRelevantProjectDependenciesToClasspath(List path) {
getLog().debug("Project Dependencies will be included.");
List artifacts = new ArrayList<>();
List theClasspathFiles = new ArrayList<>();
collectProjectArtifactsAndClasspath(artifacts, theClasspathFiles);
for (Path classpathFile : theClasspathFiles) {
getLog().debug("Adding to classpath : " + classpathFile);
path.add(classpathFile);
}
for (Artifact classPathElement : artifacts) {
if (classPathElement.getFile() != null) {
getLog().debug("Adding project dependency artifact: " + classPathElement.getArtifactId() + " to classpath");
path.add(classPathElement.getFile().toPath());
} else {
getLog().debug("! Adding project dependency artifact: " + classPathElement.getArtifactId() + " to classpath");
}
}
}
protected void collectProjectArtifactsAndClasspath(List artifacts, List theClasspathFiles) {
artifacts.add(project.getArtifact());
artifacts.addAll(project.getArtifacts());
artifacts.addAll(project.getPluginArtifacts());
artifacts.addAll(pluginDescriptor.getArtifacts());
theClasspathFiles.add(Paths.get(project.getBuild().getOutputDirectory()));
theClasspathFiles.add(Paths.get(project.getBuild().getTestOutputDirectory()));
}
private void joinNonDaemonThreads(ThreadGroup threadGroup) {
boolean foundNonDaemon;
do {
foundNonDaemon = false;
Collection threads = getActiveThreads(threadGroup);
for (Thread thread : threads) {
if (thread.isDaemon()) {
continue;
}
foundNonDaemon = true;
joinThread(thread, 0);
}
} while (foundNonDaemon);
}
private Collection getActiveThreads(ThreadGroup threadGroup) {
Thread[] threads = new Thread[threadGroup.activeCount()];
int numThreads = threadGroup.enumerate(threads);
Collection result = new ArrayList(numThreads);
for (int i = 0; i < threads.length && threads[i] != null; i++) {
result.add(threads[i]);
}
return result;
}
private void joinThread(Thread thread, long timeoutMsecs) {
try {
getLog().debug("joining on thread " + thread);
thread.join(timeoutMsecs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
getLog().warn("interrupted while joining against thread " + thread, e);
}
if (thread.isAlive()) {
getLog().warn("thread " + thread + " was interrupted but is still alive after waiting at least " + timeoutMsecs + "msecs");
}
}
private void terminateThreads(ThreadGroup threadGroup) {
long startTime = System.currentTimeMillis();
Set uncooperativeThreads = new HashSet(); // these were not responsive to interruption
for (Collection threads = getActiveThreads(threadGroup); !threads.isEmpty(); threads = getActiveThreads(threadGroup), threads.removeAll(uncooperativeThreads)) {
// Interrupt all threads we know about as of this instant (harmless if spuriously went dead (! isAlive())
// or if something else interrupted it ( isInterrupted() ).
for (Thread thread : threads) {
getLog().debug("interrupting thread " + thread);
thread.interrupt();
}
// Now join with a timeout and call stop() (assuming flags are set right)
for (Thread thread : threads) {
if (!thread.isAlive()) {
continue; // and, presumably it won't show up in getActiveThreads() next iteration
}
int daemonThreadJoinTimeout = 15000;
if (daemonThreadJoinTimeout <= 0) {
joinThread(thread, 0); // waits until not alive; no timeout
continue;
}
long timeout = daemonThreadJoinTimeout - (System.currentTimeMillis() - startTime);
if (timeout > 0) {
joinThread(thread, timeout);
}
if (!thread.isAlive()) {
continue;
}
uncooperativeThreads.add(thread); // ensure we don't process again
}
}
if (!uncooperativeThreads.isEmpty()) {
getLog().warn("NOTE: " + uncooperativeThreads.size() + " thread(s) did not finish despite being asked to "
+ " via interruption. This is not a problem with mvn plugin, it is a problem with the running code."
+ " Although not serious, it should be remedied.");
} else {
int activeCount = threadGroup.activeCount();
if (activeCount != 0) {
Thread[] threadsArray = new Thread[1];
threadGroup.enumerate(threadsArray);
getLog().debug("strange; " + activeCount + " thread(s) still active in the group " + threadGroup + " such as " + threadsArray[0]);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy