package spoon.test.main; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.Assertion; import org.junit.contrib.java.lang.system.ExpectedSystemExit; import spoon.Launcher; import spoon.reflect.code.CtArrayWrite; import spoon.reflect.code.CtAssignment; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtFieldWrite; import spoon.reflect.code.CtVariableWrite; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtShadowable; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeParameter; import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtFieldReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtBiScannerDefault; import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.filter.TypeFilter; import spoon.test.parent.ParentTest; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class MainTest { @Test public void testMain() throws Exception { // we have to remove the test-classes folder // so that the precondition of --source-classpath is not violated // (target/test-classes contains src/test/resources which itself contains Java files) StringBuilder classpath = new StringBuilder(); for (String classpathEntry : System.getProperty("java.class.path").split(File.pathSeparator)) { if (!classpathEntry.contains("test-classes")) { classpath.append(classpathEntry); classpath.append(File.pathSeparator); } } String systemClassPath = classpath.substring(0, classpath.length() - 1); Launcher launcher = new Launcher(); launcher.run(new String[] { "-i", "src/main/java", "-o", "target/spooned", "--destination","target/spooned-build", "--source-classpath", systemClassPath, "--compile", // compiling Spoon code itself on the fly "--compliance", "7", "--level", "OFF" }); checkGenericContracts(launcher.getFactory().Package().getRootPackage()); checkShadow(launcher.getFactory().Package().getRootPackage()); checkParentConsistency(launcher.getFactory().Package().getRootPackage()); } public void checkGenericContracts(CtPackage pack) { // clone checkEqualityBetweenOriginalAndClone(pack); // parent ParentTest.checkParentContract(pack); // assignments checkAssignmentContracts(pack); // scanners checkContractCtScanner(pack); // type parameter reference. checkBoundAndUnboundTypeReference(pack); } private void checkBoundAndUnboundTypeReference(CtPackage pack) { new CtScanner() { @Override public void visitCtTypeParameterReference(CtTypeParameterReference ref) { CtTypeParameter declaration = ref.getDeclaration(); if (declaration != null) { assertEquals(ref.getSimpleName(), declaration.getSimpleName()); } super.visitCtTypeParameterReference(ref); } }.scan(pack); } private void checkEqualityBetweenOriginalAndClone(CtPackage pack) { class ActualCounterScanner extends CtBiScannerDefault { @Override public boolean biScan(CtElement element, CtElement other) { if (element == null) { if (other != null) { Assert.fail("element can't be null if other isn't null."); } } else if (other == null) { Assert.fail("other can't be null if element isn't null."); } else { assertEquals(element, other); assertFalse(element == other); } return super.biScan(element, other); } } final ActualCounterScanner actual = new ActualCounterScanner(); actual.biScan(pack, pack.clone()); } private void checkShadow(CtPackage pack) { new CtScanner() { @Override public void scan(CtElement element) { if (element != null && CtShadowable.class.isAssignableFrom(element.getClass())) { assertFalse(((CtShadowable) element).isShadow()); } super.scan(element); } @Override public <T> void visitCtTypeReference(CtTypeReference<T> reference) { assertNotNull(reference); if (CtTypeReference.NULL_TYPE_NAME.equals(reference.getSimpleName()) || "?".equals(reference.getSimpleName())) { super.visitCtTypeReference(reference); return; } final CtType<T> typeDeclaration = reference.getTypeDeclaration(); assertNotNull(typeDeclaration); assertEquals(reference.getSimpleName(), typeDeclaration.getSimpleName()); assertEquals(reference.getQualifiedName(), typeDeclaration.getQualifiedName()); if (reference.getDeclaration() == null) { assertTrue(typeDeclaration.isShadow()); } super.visitCtTypeReference(reference); } @Override public <T> void visitCtExecutableReference(CtExecutableReference<T> reference) { assertNotNull(reference); if (isLanguageExecutable(reference)) { super.visitCtExecutableReference(reference); return; } final CtExecutable<T> executableDeclaration = reference.getExecutableDeclaration(); assertNotNull("cannot find decl for " + reference.toString(),executableDeclaration); assertEquals(reference.getSimpleName(), executableDeclaration.getSimpleName()); // when a generic type is used in a parameter and return type, the shadow type doesn't have these information. for (int i = 0; i < reference.getParameters().size(); i++) { if (reference.getParameters().get(i) instanceof CtTypeParameterReference) { continue; } if (reference.getParameters().get(i) instanceof CtArrayTypeReference && ((CtArrayTypeReference) reference.getParameters().get(i)).getComponentType() instanceof CtTypeParameterReference) { continue; } assertEquals(reference.getParameters().get(i).getQualifiedName(), executableDeclaration.getParameters().get(i).getType().getQualifiedName()); } if (reference.getDeclaration() == null && CtShadowable.class.isAssignableFrom(executableDeclaration.getClass())) { assertTrue(((CtShadowable) executableDeclaration).isShadow()); } super.visitCtExecutableReference(reference); } private <T> boolean isLanguageExecutable(CtExecutableReference<T> reference) { return "values".equals(reference.getSimpleName()); } @Override public <T> void visitCtFieldReference(CtFieldReference<T> reference) { assertNotNull(reference); if (isLanguageField(reference) || isDeclaredInSuperClass(reference)) { super.visitCtFieldReference(reference); return; } final CtField<T> fieldDeclaration = reference.getFieldDeclaration(); assertNotNull(fieldDeclaration); assertEquals(reference.getSimpleName(), fieldDeclaration.getSimpleName()); assertEquals(reference.getType().getQualifiedName(), fieldDeclaration.getType().getQualifiedName()); if (reference.getDeclaration() == null) { assertTrue(fieldDeclaration.isShadow()); } super.visitCtFieldReference(reference); } private <T> boolean isLanguageField(CtFieldReference<T> reference) { return "class".equals(reference.getSimpleName()) || "length".equals(reference.getSimpleName()); } private <T> boolean isDeclaredInSuperClass(CtFieldReference<T> reference) { final CtType<?> typeDeclaration = reference.getDeclaringType().getTypeDeclaration(); return typeDeclaration != null && typeDeclaration.getField(reference.getSimpleName()) == null; } }.visitCtPackage(pack); } @Test public void test() throws Exception { final Launcher spoon = new Launcher(); spoon.setArgs(new String[] {"--output-type", "nooutput" }); spoon.addInputResource("./src/test/java/spoon/test/main/testclasses"); spoon.addInputResource("./src/main/java/spoon/template/Parameter.java"); spoon.getEnvironment().setNoClasspath(true); spoon.run(); checkShadow(spoon.getFactory().Package().getRootPackage()); checkParentConsistency(spoon.getFactory().Package().getRootPackage()); } private void checkContractCtScanner(CtPackage pack) { class Counter { int scan, enter, exit = 0; } final Counter counter = new Counter(); new CtScanner() { @Override public void scan(CtElement element) { if (element != null) { counter.scan++; } super.scan(element); } @Override public void enter(CtElement element) { counter.enter++; super.enter(element); } @Override public void exit(CtElement element) { counter.exit++; super.exit(element); } }.visitCtPackage(pack); assertTrue(counter.enter == counter.exit); // there is one scan less, because we start with visit assertTrue(counter.enter == counter.scan + 1); } public static void checkAssignmentContracts(CtElement pack) { for (CtAssignment assign : pack.getElements(new TypeFilter<CtAssignment>( CtAssignment.class))) { CtExpression assigned = assign.getAssigned(); if (!(assigned instanceof CtFieldWrite || assigned instanceof CtVariableWrite || assigned instanceof CtArrayWrite)) { throw new AssertionError("AssignmentContract error:" + assign.getPosition()+"\n"+assign.toString()+"\nAssigned is "+assigned.getClass()); } } } private void checkParentConsistency(CtPackage pack) { final Set<CtElement> inconsistentParents = new HashSet<>(); new CtScanner() { private Deque<CtElement> previous = new ArrayDeque(); @Override protected void enter(CtElement e) { if (e != null) { if (!previous.isEmpty()) { try { if (e.getParent() != previous.getLast()) { inconsistentParents.add(e); } } catch (ParentNotInitializedException ignore) { inconsistentParents.add(e); } } previous.add(e); } super.enter(e); } @Override protected void exit(CtElement e) { if (e == null) { return; } if (e.equals(previous.getLast())) { previous.removeLast(); } else { throw new RuntimeException("Inconsistent stack"); } super.exit(e); } }.visitCtPackage(pack); assertEquals("All parents have to be consistent", 0, inconsistentParents.size()); } @Test public void testTest() throws Exception { // the tests should be spoonable Launcher launcher = new Launcher(); launcher.run(new String[] { "-i", "src/test/java", "-o", "target/spooned", "--noclasspath", "--compliance", "8", "--level", "OFF" }); checkGenericContracts(launcher.getFactory().Package().getRootPackage()); } @Test public void testResourcesCopiedInTargetDirectory() throws Exception { StringBuilder classpath = new StringBuilder(); for (String classpathEntry : System.getProperty("java.class.path").split(File.pathSeparator)) { if (!classpathEntry.contains("test-classes")) { classpath.append(classpathEntry); classpath.append(File.pathSeparator); } } String systemClassPath = classpath.substring(0, classpath.length() - 1); spoon.Launcher.main(new String[] { "-i", "src/test/resources/no-copy-resources/", "-o", "target/spooned-with-resources", "--destination","target/spooned-build", "--source-classpath", systemClassPath, "--compile" }); assertTrue(new File("src/test/resources/no-copy-resources/package.html").exists()); assertTrue(new File("target/spooned-with-resources/package.html").exists()); assertTrue(new File("src/test/resources/no-copy-resources/fr/package.html").exists()); assertTrue(new File("target/spooned-with-resources/fr/package.html").exists()); assertTrue(new File("src/test/resources/no-copy-resources/fr/inria/package.html").exists()); assertTrue(new File("target/spooned-with-resources/fr/inria/package.html").exists()); } @Test public void testResourcesNotCopiedInTargetDirectory() throws Exception { StringBuilder classpath = new StringBuilder(); for (String classpathEntry : System.getProperty("java.class.path").split(File.pathSeparator)) { if (!classpathEntry.contains("test-classes")) { classpath.append(classpathEntry); classpath.append(File.pathSeparator); } } String systemClassPath = classpath.substring(0, classpath.length() - 1); spoon.Launcher.main(new String[] { "-i", "src/test/resources/no-copy-resources", "-o", "target/spooned-without-resources", "--destination","target/spooned-build", "--source-classpath", systemClassPath, "--compile", "-r" }); assertTrue(new File("src/test/resources/no-copy-resources/package.html").exists()); assertFalse(new File("target/spooned-without-resources/package.html").exists()); assertTrue(new File("src/test/resources/no-copy-resources/fr/package.html").exists()); assertFalse(new File("target/spooned-without-resources/fr/package.html").exists()); assertTrue(new File("src/test/resources/no-copy-resources/fr/inria/package.html").exists()); assertFalse(new File("target/spooned-without-resources/fr/inria/package.html").exists()); } @Rule public final ExpectedSystemExit exit = ExpectedSystemExit.none(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); @Test public void testLauncherWithoutArgumentsExitWithSystemExit() throws Exception { exit.expectSystemExit(); final PrintStream oldErr = System.err; System.setErr(new PrintStream(errContent)); exit.checkAssertionAfterwards(new Assertion() { @Override public void checkAssertion() throws Exception { assertTrue(errContent.toString().contains("Usage: java <launcher name> [option(s)]")); System.setErr(oldErr); } }); new Launcher().run(new String[] { }); } }