package org.testory; import static java.util.Arrays.asList; import static javax.xml.bind.DatatypeConverter.printHexBinary; import static org.junit.Assert.assertEquals; import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashSet; import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class TestBuilding { @Rule public TemporaryFolder folder = new TemporaryFolder(); private String hashA, hashB; @Test public void build_is_deterministic() throws Exception { exec("./run/build"); hashA = sha1("/tmp/testory.jar"); exec("./run/build"); hashB = sha1("/tmp/testory.jar"); assertEquals(hashA, hashB); } private void exec(String command) throws InterruptedException, IOException { int exitCode = new ProcessBuilder(command) .redirectOutput(folder.newFile()) .redirectError(folder.newFile()) .start() .waitFor(); assume(exitCode == 0); } private static String sha1(String filename) { try { MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); DigestInputStream input = new DigestInputStream(new FileInputStream(filename), sha1); drainAllBytes(input); input.close(); return printHexBinary(input.getMessageDigest().digest()); } catch (IOException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } } private static void drainAllBytes(DigestInputStream input) throws IOException { byte[] buffer = new byte[1024]; for (int read = 0; read > -1; read = input.read(buffer)) {} } private static void assume(boolean condition) { if (!condition) { throw new RuntimeException(); } } @Test public void api_exposes_only_required_types() { assertEquals(Build.exposed, filterNonJdk(allDependencies(Build.exposed))); } private static Set<Class<?>> allDependencies(Set<Class<?>> classes) { Set<Class<?>> dependencies = new HashSet<>(); Set<Class<?>> remaining = new HashSet<>(); remaining.addAll(classes); while (!remaining.isEmpty()) { Class<?> current = remaining.iterator().next(); dependencies.add(current); remaining.addAll(directDependencies(current)); remaining.removeAll(dependencies); } return dependencies; } private static Set<Class<?>> directDependencies(Class<?> type) { Set<Class<?>> dependencies = new HashSet<>(); for (Method method : type.getMethods()) { dependencies.add(method.getReturnType()); dependencies.addAll(asList(method.getParameterTypes())); for (Annotation annotation : method.getAnnotations()) { dependencies.add(annotation.annotationType()); } for (Annotation[] parameter : method.getParameterAnnotations()) { for (Annotation annotation : parameter) { dependencies.add(annotation.annotationType()); } } } if (type.isArray()) { dependencies.add(type.getComponentType()); } return dependencies; } private static Set<Class<?>> filterNonJdk(Set<Class<?>> types) { Set<Class<?>> filtered = new HashSet<>(); for (Class<?> type : types) { if (!type.isPrimitive() && !type.isArray() && type.getClassLoader() != null) { filtered.add(type); } } return filtered; } }