package io.takari.maven.plugins.jar;
import static io.takari.maven.testing.TestMavenRuntime.newParameter;
import static io.takari.maven.testing.TestResources.cp;
import static io.takari.maven.testing.TestResources.create;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.Os;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import com.google.common.io.Files;
import io.takari.hash.FingerprintSha1Streaming;
import io.takari.incrementalbuild.maven.testing.IncrementalBuildRule;
import io.takari.maven.testing.TestResources;
public class JarTest {
@Rule
public final TestResources resources = new TestResources();
@Rule
public final IncrementalBuildRule mojos = new IncrementalBuildRule();
@Test
public void jarCreation() throws Exception {
//
// Generate some resources to JAR
//
File basedir = resources.getBasedir("jar/project-with-resources");
mojos.executeMojo(basedir, "process-resources");
File resource = new File(basedir, "target/classes/resource.txt");
assertTrue(resource.exists());
String line = Files.readFirstLine(resource, Charset.defaultCharset());
assertTrue(line.contains("resource.txt"));
//
// Generate the JAR a first time and capture the fingerprint
//
mojos.executeMojo(basedir, "jar");
File jar0 = new File(basedir, "target/test-1.0.jar");
assertTrue(jar0.exists());
String fingerprint0 = new FingerprintSha1Streaming().fingerprint(jar0);
//
// Generate the JAR a second time and ensure that the fingerprint is still the same when
// the JAR content is the same. The outer SHA1 of a JAR built at two points in time will
// be different even though the content has not changed.
//
mojos.executeMojo(basedir, "jar");
File jar1 = new File(basedir, "target/test-1.0.jar");
Assert.assertTrue(jar1.exists());
String fingerprint1 = new FingerprintSha1Streaming().fingerprint(jar1);
assertEquals("We expect the JAR to have the same fingerprint after repeated builds.", fingerprint0, fingerprint1);
// Make sure our maven properties file is written correctly
try (ZipFile zip0 = new ZipFile(jar1)) {
String pomProperties = "META-INF/maven/io.takari.lifecycle.its/test/pom.properties";
ZipEntry entry = zip0.getEntry(pomProperties);
if (entry != null) {
InputStream is = zip0.getInputStream(entry);
Properties p = new Properties();
p.load(is);
assertEquals("io.takari.lifecycle.its", p.getProperty("groupId"));
assertEquals("test", p.getProperty("artifactId"));
assertEquals("1.0", p.getProperty("version"));
} else {
fail("We expected the standard pom.properties: " + pomProperties);
}
}
try (ZipFile zip1 = new ZipFile(jar1)) {
String manifestEntryName = "META-INF/MANIFEST.MF";
ZipEntry manifestEntry = zip1.getEntry(manifestEntryName);
if (manifestEntry != null) {
InputStream is = zip1.getInputStream(manifestEntry);
Manifest p = new Manifest(is);
assertNotNull(p.getMainAttributes().getValue("Built-By"));
assertNotNull(p.getMainAttributes().getValue("Build-Jdk"));
assertEquals("1.0", p.getMainAttributes().getValue("Manifest-Version"));
assertEquals("test", p.getMainAttributes().getValue("Implementation-Title"));
assertEquals("1.0", p.getMainAttributes().getValue("Implementation-Version"));
assertEquals("io.takari.lifecycle.its", p.getMainAttributes().getValue("Implementation-Vendor-Id"));
} else {
fail("We expected the standard META-INF/MANIFEST.MF");
}
}
}
@Test
public void testBasic_attachedArtifacts() throws Exception {
File basedir = resources.getBasedir("jar/basic");
cp(basedir, "src/main/resources/resource.txt", "target/classes/resource.txt");
cp(basedir, "src/test/resources/test-resource.txt", "target/test-classes/resource.txt");
MavenProject project = mojos.readMavenProject(basedir);
mojos.executeMojo(project, "jar", newParameter("sourceJar", "true"), newParameter("testJar", "true"));
Map<String, Artifact> attachedArtifacts = new HashMap<String, Artifact>();
for (Artifact artifact : project.getAttachedArtifacts()) {
assertNull(attachedArtifacts.put(artifact.getClassifier(), artifact));
}
// main artifact (test-1.0.jar)
File jar = new File(basedir, "target/test-1.0.jar");
assertTrue(jar.exists());
assertEquals(jar, project.getArtifact().getFile());
assertEquals("jar", project.getArtifact().getType());
// sources (test-1.0-sources.jar)
File sourceJar = new File(basedir, "target/test-1.0-sources.jar");
assertTrue(sourceJar.exists());
assertEquals(sourceJar, attachedArtifacts.get("sources").getFile());
assertEquals("jar", attachedArtifacts.get("sources").getType());
// tests (test-1.0-tests.jar)
File testJar = new File(basedir, "target/test-1.0-tests.jar");
assertTrue(testJar.exists());
assertEquals(testJar, attachedArtifacts.get("tests").getFile());
assertEquals("jar", attachedArtifacts.get("tests").getType());
}
@Test
public void testClassloader_getResources() throws Exception {
File basedir = resources.getBasedir("jar/project-with-resources");
mojos.executeMojo(basedir, "process-resources");
mojos.executeMojo(basedir, "jar");
File jar = new File(basedir, "target/test-1.0.jar");
try (URLClassLoader cl = new URLClassLoader(new URL[] {jar.toURI().toURL()}, null)) {
List<URL> list = toList(cl.getResources("subdir"));
Assert.assertEquals(1, list.size());
Assert.assertTrue(list.get(0).toString(), list.get(0).toString().endsWith("test-1.0.jar!/subdir"));
}
}
@Test
public void testJarEntries() throws Exception {
File basedir = resources.getBasedir("jar/project-with-resources");
mojos.executeMojo(basedir, "process-resources");
mojos.executeMojo(basedir, "jar");
File jar = new File(basedir, "target/test-1.0.jar");
assertZipEntries(jar //
, "D META-INF/ 315561600000" //
, "F META-INF/MANIFEST.MF 315561600000" //
, "D META-INF/maven/ 315561600000" //
, "D META-INF/maven/io.takari.lifecycle.its/ 315561600000" //
, "D META-INF/maven/io.takari.lifecycle.its/test/ 315561600000" //
, "F META-INF/maven/io.takari.lifecycle.its/test/pom.properties 315561600000" //
, "F resource.txt 315561600000" //
, "D subdir/ 315561600000" //
, "F subdir/resource.txt 315561600000" //
);
}
private static void assertZipEntries(File zipFile, String... expected) throws IOException {
try (ZipFile zip = new ZipFile(zipFile)) {
Map<String, ZipEntry> sorted = new LinkedHashMap<>();
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
sorted.put(entry.getName(), entry);
}
StringBuilder actualSb = new StringBuilder();
for (ZipEntry entry : sorted.values()) {
actualSb.append(entry.isDirectory() ? "D " : "F ").append(entry.getName()).append(' ').append(entry.getTime()).append('\n');
}
StringBuilder expectedSb = new StringBuilder();
for (String str : expected) {
expectedSb.append(str).append('\n');
}
Assert.assertEquals(expectedSb.toString(), actualSb.toString());
}
}
private static <T> List<T> toList(Enumeration<T> e) {
ArrayList<T> l = new ArrayList<T>();
while (e.hasMoreElements()) {
l.add(e.nextElement());
}
return l;
}
@Test
public void testCustomManifest() throws Exception {
File basedir = resources.getBasedir("jar/project-with-manifest");
new File(basedir, "target/classes").mkdirs(); // TODO this shouldn't be necessary
mojos.executeMojo(basedir, "jar");
try (JarFile jar = new JarFile(new File(basedir, "target/test-1.0.jar"))) {
Manifest mf = jar.getManifest();
Assert.assertEquals("custom-value", mf.getMainAttributes().getValue("Custom-Entry"));
}
}
@Test
@Ignore("this is currently broken, but the fix requires changes to incrementalbuild")
public void testCustomManifest_incremental() throws Exception {
File basedir = resources.getBasedir("jar/project-with-manifest");
new File(basedir, "target/classes").mkdirs(); // TODO this shouldn't be necessary
mojos.executeMojo(basedir, "jar");
try (JarFile jar = new JarFile(new File(basedir, "target/test-1.0.jar"))) {
Manifest mf = jar.getManifest();
Assert.assertEquals("custom-value", mf.getMainAttributes().getValue("Custom-Entry"));
}
cp(basedir, "src/META-INF/MANIFEST.MF-changed", "src/META-INF/MANIFEST.MF");
mojos.executeMojo(basedir, "jar");
try (JarFile jar = new JarFile(new File(basedir, "target/test-1.0.jar"))) {
Manifest mf = jar.getManifest();
Assert.assertEquals("changed-custom-value", mf.getMainAttributes().getValue("Custom-Entry"));
}
}
@Test
public void testDuplicateCustomManifest() throws Exception {
File basedir = resources.getBasedir("jar/project-with-manifest-under-target-classes");
mojos.executeMojo(basedir, "jar");
try (JarFile jar = new JarFile(new File(basedir, "target/test-1.0.jar"))) {
// make sure there is only one manifest entry
int count = 0;
for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
if ("META-INF/MANIFEST.MF".equalsIgnoreCase(entry.getName())) {
count++;
}
}
Assert.assertEquals(1, count);
// now check the manifest contents
Manifest mf = jar.getManifest();
Assert.assertEquals("custom-value", mf.getMainAttributes().getValue("Custom-Entry"));
}
}
@Test
public void testEmpty() throws Exception {
File basedir = resources.getBasedir("jar/empty");
mojos.executeMojo(basedir, "jar");
assertTrue(new File(basedir, "target/test-1.0.jar").exists());
assertTrue(new File(basedir, "target/test-1.0-sources.jar").exists());
assertFalse(new File(basedir, "target/test-1.0-tests.jar").exists());
}
@Test
public void testSymlinkedDirectories() throws Exception {
Assume.assumeTrue(Os.isFamily(Os.FAMILY_UNIX));
File basedir = resources.getBasedir();
File orig = new File(basedir, "orig");
create(orig, "src/main/java/pkg/Class.java");
create(orig, "src/test/java/testpkg/Test.java");
create(orig, "target/classes/resource.txt", "target/classes/subdir/resource.txt");
java.nio.file.Files.createSymbolicLink(new File(orig, "target/classes/symlinked-resource.txt").toPath(), new File(orig, "target/classes/resource.txt").toPath());
create(orig, "target/test-classes/test-resource.txt");
File symlink = java.nio.file.Files.createSymbolicLink(new File(basedir, "symlink").toPath(), orig.toPath()).toFile();
mojos.executeMojo(symlink, "jar", newParameter("testJar", "true"), newParameter("sourceJar", "true"));
assertZipEntries(new File(symlink, "target/test-1.jar") //
, "D META-INF/ 315561600000" //
, "F META-INF/MANIFEST.MF 315561600000" //
, "D META-INF/maven/ 315561600000" //
, "D META-INF/maven/test/ 315561600000" //
, "D META-INF/maven/test/test/ 315561600000" //
, "F META-INF/maven/test/test/pom.properties 315561600000" //
, "F resource.txt 315561600000" //
, "D subdir/ 315561600000" //
, "F subdir/resource.txt 315561600000" //
, "F symlinked-resource.txt 315561600000" //
);
assertZipEntries(new File(symlink, "target/test-1-tests.jar") //
, "D META-INF/ 315561600000" //
, "F META-INF/MANIFEST.MF 315561600000" //
, "F test-resource.txt 315561600000" //
);
assertZipEntries(new File(symlink, "target/test-1-sources.jar") //
, "D META-INF/ 315561600000" //
, "F META-INF/MANIFEST.MF 315561600000" //
, "D pkg/ 315561600000" //
, "F pkg/Class.java 315561600000" //
);
}
}