"Unit testing" a Maven build - maven

I have a complex Maven build, and I would like to have some automated check that the JAR files and POM files produced by the build actually contain what I expect them to contain.
For instance, I would like to check for the presence of an Automatic-Module-Name entry in the manifest file. It's a multi-release JAR, so I would like to check that the proper class files exist inside of META-INF/versions. I'm going to publish to Maven Central, so I'd also like to check that the produced pom file contains the dependencies the project needs, but that the produced pom file for the fat jar that I also publish, doesn't contain these dependencies.
Basically, I'd like to unit test my build :).
Unfortunately, it's hard to google for this, because of the words I would use to describe this ("test", "verify") already have very specific different meanings in Maven.
Is there a nice way to do this? I would prefer a Maven plugin, since I'm obviously already using that, but I'm open to other things too.

I ended up, as #khmarbaise suggested in the comments, creating a new Maven submodule. In its pom I used the copy-rename-maven-plugin to copy over the files I actually want to check, like this:
<plugin>
<groupId>com.coderplus.maven.plugins</groupId>
<artifactId>copy-rename-maven-plugin</artifactId>
<version>${version.copy-rename-maven-plugin}</version>
<executions>
<execution>
<id>copy-artifacts</id>
<phase>compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<sourceFile>${project.basedir}../core/target/.flattened-pom.xml</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/flattened.pom</destinationFile>
</fileSet>
<fileSet>
<sourceFile>${project.basedir}../core/target/myArtifactId-${project.version}.jar</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/myArtifactId.jar</destinationFile>
</fileSet>
<!-- more fileSets here -->
</fileSets>
</configuration>
</execution>
</executions>
</plugin>
Then I was able to read the pom file and do assertions on it. I ended up using Java's built-in XPath API, but you can use whatever.
I was also able to read the JAR file by turning it into a NIO FileSystem:
var filename = "myArtifactId.jar"; // or "flattened.pom"
var file = getClass().getClassLoader().getResource(filename);
var uri = URI.create("jar:" + file.toURI().toString());
FileSystem fs = FileSystems.newFileSystem(uri, Map.of());
You can get a list of files:
var path = fs.getPath("/");
Set<String> filenames = StreamSupport
.stream(walk.spliterator(), false)
.map(Path::toString)
.collect(Collectors.toSet());
Or read the content of a file:
var path = fs.getPath("/META-INF/MANIFEST.MF");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
String content = out.toString();
assertTrue(content.contains("Multi-Release: true"));
var path = fs.getPath("/com/example/MyClass.class");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
byte[] content = out.toByteArray();
var actualVersion = content[7]; // the major version of the class file is at this location
assertEquals(52, actualVersion); // 52 = Java 8
(Note that for this answer, I didn't bother to handle exception or close resources; you'll have to do that yourself.)

Related

Maven Enforcer: How to access maven properties from beanshell rule

I successfully created a evaluateBeanshell rule with the maven-enforcer-plugin that scans files in the workspace for common mistakes.
With a hardcoded path the rule works fine. When I want to use the ${project.basedir} variable from the surrounding pom, the script breaks on my Windows machine.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
<executions>
<execution>
<id>enforce-banned-dependencies</id>
<inherited>true</inherited>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<evaluateBeanshell>
<condition>
import scanner.MyScanner;
scanner = new MyScanner();
//hack to read root dir
//project.basedir crashes beanshell with its backslashes
rootPath = new File("");
root = new File(rootPath.getAbsolutePath());
print("Scanning in: " + root);
print("${project.artifactId}"); // works fine
print("${project.basedir}"); // breaks the code
scanner.loopThroughProjects(root);
return everythingIsFine;
</condition>
</evaluateBeanshell>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
In the debug output the line:
print("${project.basedir}");
was replaced by:
print("D:\code\my-maven-project");
Is there another maven property with sanitized slashes or is there another way to access ${project.basedir}?
The hack outlined in the code example kind of works, but I don't like hacks that force me to leave comments.
You could try ${project.baseUri}.
See https://maven.apache.org/ref/3.8.5/maven-model-builder/#Model_Interpolation
On my Windows 10 machine with Java 8 and Maven 3 the following test properties in pom.xml:
<test>${project.baseUri}</test>
<test2>${project.basedir}</test2>
become the following in the 'effective-pom' (via Intellij IDEA maven plugin)
<test>file:/D:/test/path/</test>
<test2>D:\test\path</test2>
This is just as a proof of concept to show the path separators change, and become valid as a Java String.
You could then transform the URI to a file for your needs in the beanshell script as follows:
uri = java.net.URI.create("${project.baseUri}");
root = new java.io.File(uri);
Via https://docs.oracle.com/javase/8/docs/api/java/io/File.html#File-java.net.URI-

How do I access maven project version in javadoc overview page?

I am using PDFDoclet with maven-javadoc-plugin and I've come quite a long way with it now. I have the maven and javadoc config almost at a point that is good enough but my immediate problem now is that I can't work out how to push the project version number into the PDF title page.
Before you leap to answer my question by telling me to use maven's <resource> filtering, let me outline why that isn't working.
Filtering works by taking the original file from somewhere in the src folder, doing variable substitution and putting the output in the target folder.
Javadoc works by reading files in src/main/java and src/main/javadoc and AFAIK outputting the results into target. This means filtering is useless for Javadoc since it won't read anything from target
My results show that any maven variables in javadoc comments don't get substituted.
What trick can I use to get those variables substituted into the javadoc?
The solution can't involve filtering the javadoc output after the site:site task, unless resource filtering works on PDFs.
This is the configuration, FWIW:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<configuration>
<show>package</show>
<docfilessubdirs>true</docfilessubdirs>
<tags>
<tag>
<name>pdfInclude</name>
<placement>X</placement>
<head></head>
</tag>
</tags>
</configuration>
<reportSets>
<reportSet>
<id>PDF</id>
<reports>
<report>javadoc</report>
</reports>
<configuration>
<name>PDF</name>
<description>PDF doc</description>
<destDir>pdf</destDir>
<doclet>com.tarsec.javadoc.pdfdoclet.PDFDoclet</doclet>
<docletPath>${basedir}/pdfdoclet/pdfdoclet-1.0.3-all.jar</docletPath>
<useStandardDocletOptions>false</useStandardDocletOptions>
<additionalparam>-pdf my_tech_doc-${project.version}.pdf
-config ${basedir}/pdfdoclet/pdfdoclet.properties</additionalparam>
</configuration>
</reportSet>
</reportSets>
</plugin>
and the pdfdoclet.properties:
# http://pdfdoclet.sourceforge.net/configuration.html
#
#Lets the doclet print additional output in the console and to a logfile.
debug=true
#Print "Author" tags
author=false
#Print "Version" tags
version=true
#Print "since" tags
tag.since=true
#Create summary tables
summary.table=false
#Create hyperlinks
create.links=true
#Encrypt the PDF file
encrypted=false
#Allow printing of the PDF file
allow.printing=true
#Create a bookmark frame
create.frame=true
#Print a title page
api.title.page=true
api.copyright=None
api.author=Hansruedi
#Enables the tag-based filter
filter=true
filter.tags=pdfInclude
font.text.name=resources/arial.ttf
page.orientation=portrait
The PDFDoclet-specific api.* properties should result in a title page as the first page of the PDF, but it doesn't work. If there is a trick that I've missed here and I could get that title page produced, then that might also allow a solution for this somehow.
I realised I can do a quick and dirty hack with the maven <resources> functionality:
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>${basedir}/src/main/javadoc</targetPath>
<filtering>true</filtering>
<includes>
<include>**/overview.html</include>
</includes>
</resource>
This copies my overview.html and filters it, outputting it into the javadoc source directory.
The dirtiness is that this filtered version could then accidentally end up under version control, although using svn I can add it to the ignore list.

How can I ban a Maven dependency where the version contains a certain part?

I'm tryin to use Maven Enforcer's banned dependencies where I want to ban that there are compile and runtime dependencies to any artifact that contains -redhat-. The background of this: The JEE API and other stuff already exists in the JBoss AS and should never be included in the EAR.
This is what I'm trying, but it doesn't work:
<execution>
<id>banned-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<searchTransitive>false</searchTransitive>
<excludes>
<exclude>*:*:*-redhat-*:*:compile</exclude>
<exclude>*:*:*-redhat-*:*:runtime</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
As you have discovered this wont work the way you are wanting. (I presume you are finding that the enforcer plugin is matching all dependencies listed in your pom?)
The problem is that Maven expects the version given to either be a single * or to conform to maven's Version Spec. (i.e. 1.0, [1.0,) etc) It can't handle the multiple wildcards that you are using.
Unfortunately I don't really have a solution for you. You could potentially
Write Your Own Rule and extend the BannedDependencies rule and have it work the way you would like.
What follows is a dive into the code that is causing your issue
In the BannedDependencies class there is the following check for the version given in the exclude string:
if (pattern[2].equals("*") || artifact.getVersion().equals(pattern[2]) ) {
result = true;
} else {
try {
result = AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()));
} catch ( InvalidVersionSpecificationException e ) {
throw new EnforcerRuleException("Invalid Version Range: ", e);
}
}
The specific problem for you is
AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()))
You can see that it is expecting a VersionRange due to VersionRange.createFromVersionSpec(). The code for that can be seen here:
VersionRange source code

Custom values in Maven pom.properties file

I'm trying to add custom values in the pom.properties file that Maven generates in the META-INF/maven/${groupId}/${artifactId} location
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<build>${BUILD_TAG}</build>
</manifestEntries>
<addMavenDescriptor>true</addMavenDescriptor>
<pomPropertiesFile>${project.build.directory}\interface.properties</pomPropertiesFile>
</archive>
</configuration>
</plugin>
The content of the interface.properties files is
# Build Properties
buildId=746
Using the documentation I have pointed the pomPropertiesFile element to an external properties, but the generated pom.properties file still has the default content after running mvn install
What's the correct usage of the pomPropertiesFile element?
EDIT
I believe that the problem lies in org.apache.maven.archiver.PomPropertiesUtil. If you look at the method sameContents in the source it returns true if the properties in the external file are the same as the defaults and false if different. If the result of sameContents is false, then the contents of the external file are ignored.
Sure enough, this has already been logged as a bug
I think you need to place a file under src/main/resources/META-INF/${groupId}/${artifactId}/interface.properties and let maven do the filtering job (configure the filtering). The file will automatically be copied to target/META-INF/maven/${groupId}/${artifactId}/ location.
See https://issues.apache.org/jira/browse/MNG-4998
Maven 3 will resolve property placeholders eagerly when reading pom.xml for all properties values that are available at this time. Modifying these properties at a later time will not affect the values that are already resolved in pom.xml.
However, if property value is not available (there's no default), then placeholder will not be replaced by the value and it can still be processed later as a placeholder. For example, if a plugin will generate some property during the build, or if placeholder is read and processed by a plugin during some build step.

Maven 3: Assembling a Jar file containing binary resources

I have setup a Maven project consisting of two child modules, one Java Jar module and one creating a Windows Executable using NPanday. My build is working great.
The problem I am having, is that I would like to create a Jar file containing my Java lib and have the Exe file embedded so I can load it as a resource from the code inside the lib.
It seems the assembly plugin would be the path to go, but I am having some trouble configuring this. I don't even know if this is the correct path to go in this case.
Could someone here please guide me to the right path or give me a hint as to how such an assembly descriptor should look like?
Chris
Well I have a Java Project, that only contains a test Class for now, as I am still in the stage of setting up my build:
Module de.cware.utils:lib-psexec-client:
/de/cware/utils/psexec/client/Test.java
Module de.cware.utils:lib-psexec-service:
outputs a file called "service.exe"
I want the output to look like the client jar, but to also contain the "service.exe" so I can load it from the code in the Client jar.
Module de.cware.utis:lib-psexec-assembly:
/de/cware/utils/psexec/client/Test.java
/service.exe
Ok ... so it seems I sorted out a solution on my own. I know this question was relatively special again ... as all of my questions seem to be :-)
The solution was to create a maven module containing a custom implementation of a PlexusIoResourceCollection and to reference this from a components.xml file in the "META-INF/plexus" directory.
After adding this as a dependency to my assembly plugin, I was able to embed the exe files into my jar :-)
Here comes the code of the component:
package npanday.plugin.archiver;
import org.codehaus.plexus.components.io.resources.PlexusIoCompressedFileResourceCollection;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created with IntelliJ IDEA.
* User: cdutz
* Date: 02.03.12
* Time: 12:04
*/
public class PlexusIoExeResourceCollection extends PlexusIoCompressedFileResourceCollection {
#Override
protected String getDefaultExtension() {
return ".exe";
}
#Override
protected InputStream getInputStream(File file) throws IOException {
// Simply return an InputStream to the resource file.
// This will make it embed the source as a whole.
return new FileInputStream(file);
}
#Override
public String getPath() {
// Without overriding this, the exe would be included with its full path.
// This way it is included directly in the root of the result archive.
return super.getFile().getName();
}
}
Here the config xml in META-INF/plexus/components.xml
<component-set>
<components>
<component>
<role>org.codehaus.plexus.components.io.resources.PlexusIoResourceCollection</role>
<role-hint>exe</role-hint>
<implementation>npanday.plugin.archiver.PlexusIoExeResourceCollection</implementation>
<instantiation-strategy>per-lookup</instantiation-strategy>
</component>
</components>
</component-set>
And finally the usage in my assembly plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.npanday.plugins</groupId>
<artifactId>maven-exe-archiver-plugin</artifactId>
<version>${npanday.version}</version>
</dependency>
</dependencies>
</plugin>
Hopefully it will do the trick for me.

Resources