Eclipse freezes on save

The symptoms: I use Eclipse Luna on Ubuntu 14.04.
My workspace is quite old and contains many projects. At some moment, I could not save any file anymore. When clicking Ctrl + S, Eclipse was freezing forever.

The cure: I deleted the projects I was not working on anymore. Reducing the number of files managed in my workspace solved the problem.

Notice this could also be due to an OS limit.
Or it could be due to the number of files managed by the workspace. In any case, clean it up. Save actions should work again.

Passing parameters to a JUnit runner

This article explains how to pass parameters to a JUnit runner (through annotated test classes). Although I used an extension of Pax-Exam’s JUnit runner, this can be applied to any JUnit runner.

In few words…

I have a custom JUnit runner that had to check assertions on the test environment before running tests. By assertions, I mean “is Docker installed on the machine?”, “is RabbitMQ installed?”, “is RabbitMQ installed with credentials user/pwd?”, etc.

These tests are not unit tests, but integration tests.
And they rely on JUnit. Or more exactly, they rely on Pax-Exam, which itself rely on JUnit. Pax-Exam is a tool that allows to run tests within an OSGi environment (Apache Karaf in my case). Tests that use a probe work as follows: first, the OSGi container is deployed and/or started. Then, a probe is injected in this container (as an OSGi bundle) and it is this probe that runs the tests.

Usually, with JUnit, one would use the Assume functions to verify a test can run. If a condition verified by Assume is not satisfied, then the test is skipped. With Pax-Exam, this would not be very efficient, because before running the test, and thus detecting an assumption is wrong, the OSGi container would have been set up. And this takes time. What I wanted was to verify assertions on the environment BEFORE the container was set up. So, I sub-classed Pax’s runner, which itself extends JUnit’s runner.

And since not all my tests have the same requirements, I had to find a way to pass parameters to my JUnit runner. Notice this is not parameterized tests. Parameterized tests imply passing parameters to a test class. What I wanted was to pass parameters to the runner (the class that runs the tests) through the test class.

The solution

Well, nothing original here. I created a custom Java annotation.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RoboconfITConfiguration {

	/**
	 * @return true if the test requires RabbitMQ running with default credentials
	 */
	boolean withRabbitMq() default true;

	/**
	 * @return true if the test requires RabbitMQ running with "advanced" credentials
	 */
	boolean withComplexRabbitMq() default false;

	/**
	 * @return true if the test required Docker to be installed on the local machine
	 */
	boolean withDocker() default false;
}

It is used by my test classes to specify assertions on the environment.

@RunWith( RoboconfPaxRunner.class )
@RoboconfITConfiguration( withDocker = true, withComplexRabbitMq = true )
@ExamReactorStrategy( PerMethod.class )
public class LocalDockerWithAgentChecksTest {

When my runner loads the test class, it verifies if it is annotated. And if so, it verifies the assumptions on the environment. If one fails, then the test is skipped. This has the advantage of being able to compile and run as much tests as possible, even when the compile environment is not complete.

Here is the global look of my runner.
It only adds an overlay on the default Pax-Exam runner.

public class RoboconfPaxRunner extends PaxExam {

	private final Class<?> testClass;


	/**
	 * Constructor.
	 * @param klass
	 * @throws InitializationError
	 */
	public RoboconfPaxRunner( Class<?> klass ) throws InitializationError {
		super( klass );
		this.testClass = klass;
	}


	@Override
	public void run( RunNotifier notifier ) {

		boolean runTheTest = true;
		if( this.testClass.isAnnotationPresent( RoboconfITConfiguration.class )) {
			RoboconfITConfiguration annotation = this.testClass.getAnnotation( RoboconfITConfiguration.class );

			// Default RMQ settings
			if( annotation.withRabbitMq()
					&& ! RabbitMqTestUtils.checkRabbitMqIsRunning()) {
				Description description = Description.createSuiteDescription( this.testClass );
				notifier.fireTestAssumptionFailed( new Failure( description, new Exception( "RabbitMQ is not running." )));
				runTheTest = false;
			}

			// Advanced RMQ settings
			else { /* etc. */ }
		}

		// No annotation? Consider RMQ must be installed by default.
		else if( ! RabbitMqTestUtils.checkRabbitMqIsRunning()) {
			Description description = Description.createSuiteDescription( this.testClass );
			notifier.fireTestAssumptionFailed( new Failure( description, new Exception( "RabbitMQ is not running." )));
			runTheTest = false;
		}

		// If everything is good, run the test
		if( runTheTest )
			super.run( notifier );
	}
}

Finding dependencies artifacts in your Maven plug-in

This article explains how to retrieve the location of dependency artifacts in a Maven plug-in. This solution only applies to Maven 3 (and more exactly, for versions >= 3.1). Indeed, from version 3.1, Maven’s repository management library, Aether, is hosted by Eclipse and not by Sonatype anymore.

What this article explains is not new.
I just gathered various pieces from here and there. However, they were not that easy to find and this is why I wrote this post.

Here is the beginning of the story.
I have a Maven plug-in with its own project type. The packaging is a ZIP file. And dependencies to the same kind of project can be specified in the POM. So, basically, my plug-in needed to be able to locate a dependency’s built package, be it on a remote Maven repository, in the local repository, or even in the reactor. Looking into the local repository was not hard.

Injecting the local repository component in my mojo was working fine.

@Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
private ArtifactRepository local;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {

	for( Artifact unresolvedArtifact : this.project.getDependencyArtifacts()) {

		// Find the artifact in the local repository.
		Artifact art = this.local.find( unresolvedArtifact );

		File file = art.getFile();
		// ...
}

What was more complicated was dealing with reactor packaging.
What does it mean? If you type in mvn clean install, Maven will package your projects and copy the artifacts in the local repository. But, if you type in mvn clean package, the built artifacts will not be copied to the local repository. They will only be available in the target directory.

Consequently, if you have a multi-module project and dependencies across these modules (one of them depends on another one), and that you type mvn clean package, the build will fail because one of the dependencies was not resolved. In fact, Maven, or at least, Maven 3, is totally able of resolving such an artifact. The artifact location will not be in the local repository but under the module’s target directory. You only need to know which code use.

The solution for this problem lies into a single thing. We address remote, local and reactor dependencies resolution in the same way.

package whatever;

import java.io.File;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;

@Mojo( name="mojoName" )
public class ReactorDependenciesResolverMojo extends AbstractMojo {

	@Parameter( defaultValue = "${project}", readonly = true )
	private MavenProject project;

	@Component
	private RepositorySystem repoSystem;

	@Parameter( defaultValue = "${repositorySystemSession}", readonly = true, required = true )
	private RepositorySystemSession repoSession;

	@Parameter( defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true )
	private List<RemoteRepository> repositories;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {

		for( Artifact unresolvedArtifact : this.project.getDependencyArtifacts()) {

			// Here, it becomes messy. We ask Maven to resolve the artifact's location.
			// It may imply downloading it from a remote repository,
			// searching the local repository or looking into the reactor's cache.

			// To achieve this, we must use Aether
			// (the dependency mechanism behind Maven).
			String artifactId = unresolvedArtifact.getArtifactId();
			org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(
					unresolvedArtifact.getGroupId(),
					unresolvedArtifact.getArtifactId(),
					unresolvedArtifact.getClassifier(),
					unresolvedArtifact.getType(),
					unresolvedArtifact.getVersion());

			ArtifactRequest req = new ArtifactRequest().setRepositories( this.repositories ).setArtifact( aetherArtifact );
			ArtifactResult resolutionResult;
			try {
				resolutionResult = this.repoSystem.resolveArtifact( this.repoSession, req );

			} catch( ArtifactResolutionException e ) {
				throw new MojoExecutionException( &quot;Artifact &quot; + artifactId + &quot; could not be resolved.&quot;, e );
			}

			// The file should exists, but we never know.
			File file = resolutionResult.getArtifact().getFile();
			if( file == null || ! file.exists()) {
				getLog().warn( "Artifact " + artifactId + " has no attached file. Its content will not be copied in the target model directory." );
				continue;
			}

			// Do whatever you want with the file...
		}
	}
}

I also created a Gist about it.
If you want an example of a unit test that configures such a mojo, please refer to the Gist. And if you want a real condition tests, consider writing integration tests with the Maven invoker plugin, rather than (only) relying on unit tests.