// Copyright © 2011-2016, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package fi.jumi.test;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.io.ByteStreams;
import fi.jumi.launcher.daemon.EmbeddedDaemonJar;
import fi.jumi.test.PartiallyParameterized.NonParameterized;
import fi.luontola.buildtest.*;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import javax.annotation.concurrent.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import static fi.luontola.buildtest.AsmMatchers.*;
import static fi.luontola.buildtest.AsmUtils.annotatedWithOneOf;
import static java.util.Arrays.asList;
import static org.fest.assertions.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@RunWith(PartiallyParameterized.class)
public class BuildTest {
private static final String MANIFEST = "META-INF/MANIFEST.MF";
private static final String POM_FILES = "META-INF/maven/fi.jumi/";
private static final String ACTORS_POM_FILES = "META-INF/maven/fi.jumi.actors/";
private static final String BASE_PACKAGE = "fi/jumi/";
private static final String[] DOES_NOT_NEED_JSR305_ANNOTATIONS = {
// shaded classes
"fi/jumi/core/INTERNAL/",
"fi/jumi/daemon/INTERNAL/",
"fi/jumi/launcher/INTERNAL/",
// generated classes
"fi/jumi/core/events/",
"fi/jumi/launcher/events/",
// XXX: irrelevant classes generated by Retrolambda
"fi/jumi/launcher/remote/DaemonListener$",
};
private final String artifactId;
private final Integer[] expectedClassVersion;
private final List<String> expectedDependencies;
private final List<String> expectedContents;
private final Deprecations expectedDeprecations;
public BuildTest(String artifactId, List<Integer> expectedClassVersion, List<String> expectedDependencies, List<String> expectedContents, Deprecations expectedDeprecations) {
this.artifactId = artifactId;
this.expectedClassVersion = expectedClassVersion.toArray(new Integer[expectedClassVersion.size()]);
this.expectedDependencies = expectedDependencies;
this.expectedContents = expectedContents;
this.expectedDeprecations = expectedDeprecations;
}
@Parameters(name = "{0}")
public static Collection<Object[]> data() {
// TODO: upgrade shaded dependencies to Java 6/7 to benefit from their faster class loading
return asList(new Object[][]{
{"jumi-api",
asList(Opcodes.V1_7),
asList(),
asList(
MANIFEST,
POM_FILES,
BASE_PACKAGE + "api/"),
new Deprecations()
},
{"jumi-core",
asList(Opcodes.V1_2, Opcodes.V1_5, Opcodes.V1_6, Opcodes.V1_7),
asList(
"fi.jumi:jumi-api",
"fi.jumi.actors:jumi-actors"),
asList(
MANIFEST,
POM_FILES,
BASE_PACKAGE + "core/"),
new Deprecations()
},
{"jumi-daemon",
asList(Opcodes.V1_2, Opcodes.V1_5, Opcodes.V1_6, Opcodes.V1_7),
asList(),
asList(
MANIFEST,
POM_FILES,
ACTORS_POM_FILES,
BASE_PACKAGE + "actors/",
BASE_PACKAGE + "api/",
BASE_PACKAGE + "core/",
BASE_PACKAGE + "daemon/"),
new Deprecations()
},
{"jumi-launcher",
asList(Opcodes.V1_6, Opcodes.V1_7),
asList(
"fi.jumi:jumi-core"),
asList(
MANIFEST,
POM_FILES,
BASE_PACKAGE + "launcher/"),
new Deprecations()
},
});
}
@Test
public void pom_contains_only_allowed_dependencies() throws Exception {
List<String> dependencies = MavenUtils.getRuntimeDependencies(getProjectPom());
assertThat("dependencies of " + artifactId, dependencies, is(expectedDependencies));
}
@Test
public void jar_contains_only_allowed_files() throws Exception {
JarUtils.assertContainsOnly(getProjectJar(), expectedContents);
}
@Test
public void jar_contains_a_pom_properties_with_the_maven_artifact_identifiers() throws IOException {
Properties p = getPomProperties();
assertThat("groupId", p.getProperty("groupId"), is("fi.jumi"));
assertThat("artifactId", p.getProperty("artifactId"), is(artifactId));
assertThat("version", p.getProperty("version"), is(TestEnvironment.VERSION_NUMBERING));
}
@Test
public void release_jar_contains_build_properties_with_the_Git_revision_ID() throws IOException {
assumeReleaseBuild();
Properties p = getBuildProperties();
assertThat(p.getProperty("revision")).as("revision").matches("[0-9a-f]{40}");
}
@Test
@NonParameterized
public void embedded_daemon_jar_is_exactly_the_same_as_the_published_daemon_jar() throws IOException {
EmbeddedDaemonJar embeddedJar = new EmbeddedDaemonJar();
Path publishedJar = TestEnvironment.ARTIFACTS.getProjectJar("jumi-daemon").toPath();
try (InputStream in1 = embeddedJar.getDaemonJarAsStream();
InputStream in2 = Files.newInputStream(publishedJar)) {
assertTrue("the embedded daemon JAR was not equal to " + publishedJar,
Arrays.equals(ByteStreams.toByteArray(in1), ByteStreams.toByteArray(in2)));
}
}
@Test
public void none_of_the_artifacts_may_have_dependencies_to_external_libraries() {
for (String dependency : expectedDependencies) {
assertThat("artifact " + artifactId, dependency,
either(startsWith("fi.jumi:")).or(startsWith("fi.jumi.actors:")));
}
}
@Test
public void none_of_the_artifacts_may_contain_classes_from_external_libraries_without_shading_them() {
for (String content : expectedContents) {
assertThat("artifact " + artifactId, content, Matchers.
either(startsWith(BASE_PACKAGE))
.or(startsWith(POM_FILES))
.or(startsWith(ACTORS_POM_FILES))
.or(startsWith(MANIFEST)));
}
}
@Test
public void all_classes_must_use_the_specified_bytecode_version() throws IOException {
CompositeMatcher<ClassNode> matcher = newClassNodeCompositeMatcher()
.assertThatIt(hasClassVersion(isOneOf(expectedClassVersion)));
JarUtils.checkAllClasses(getProjectJar(), matcher);
}
@Test
public void all_classes_must_be_annotated_with_JSR305_concurrent_annotations() throws Exception {
CompositeMatcher<ClassNode> matcher = newClassNodeCompositeMatcher()
.excludeIf(nameStartsWithOneOf(DOES_NOT_NEED_JSR305_ANNOTATIONS))
.excludeIf(is(anInterface()))
.excludeIf(is(syntheticClass()))
.excludeIf(is(anonymousClass()))
.assertThatIt(is(annotatedWithOneOf(Immutable.class, NotThreadSafe.class, ThreadSafe.class)));
JarUtils.checkAllClasses(getProjectJar(), matcher);
}
@Test
public void deprecated_methods_are_removed_after_the_transition_period() throws IOException {
// TODO: remove duplication between the "INTERNAL" here and in DOES_NOT_NEED_JSR305_ANNOTATIONS?
expectedDeprecations.verify(
FluentIterable.from(new ClassesInJarFile(getProjectJar()))
.filter(Predicates.not(cn -> cn.name.contains("/INTERNAL/"))));
}
// helper methods
private File getProjectPom() throws IOException {
return TestEnvironment.ARTIFACTS.getProjectPom(artifactId);
}
private File getProjectJar() throws IOException {
return TestEnvironment.ARTIFACTS.getProjectJar(artifactId);
}
private void assumeReleaseBuild() throws IOException {
String version = getPomProperties().getProperty("version");
assumeTrue(TestEnvironment.VERSION_NUMBERING.isRelease(version));
}
private Properties getBuildProperties() throws IOException {
return getMavenArtifactProperties(getProjectJar(), "build.properties");
}
private Properties getPomProperties() throws IOException {
return getMavenArtifactProperties(getProjectJar(), "pom.properties");
}
private Properties getMavenArtifactProperties(File jarFile, String filename) {
return JarUtils.getProperties(jarFile, POM_FILES + artifactId + "/" + filename);
}
}