/* * Copyright 2013 eXo Platform SAS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package juzu.impl.compiler; import junit.framework.AssertionFailedError; import juzu.impl.common.FileKey; import juzu.impl.common.Name; import juzu.impl.common.Resource; import juzu.impl.common.Timestamped; import juzu.impl.fs.spi.ReadFileSystem; import juzu.impl.fs.spi.ReadWriteFileSystem; import juzu.impl.fs.spi.disk.DiskFileSystem; import juzu.impl.fs.spi.jar.JarFileSystem; import juzu.impl.fs.spi.ram.RAMFileSystem; import juzu.impl.common.Tools; import juzu.impl.metamodel.AnnotationState; import juzu.test.AbstractTestCase; import juzu.test.CompilerAssert; import juzu.test.JavaCompilerProvider; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.regex.Matcher; /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ @RunWith(value = Parameterized.class) public class CompilationTestCase extends AbstractTestCase { @Parameterized.Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{{JavaCompilerProvider.JAVAC},{JavaCompilerProvider.ECJ}}); } /** . */ private final JavaCompilerProvider compilerProvider; public CompilationTestCase(JavaCompilerProvider compilerProvider) { this.compilerProvider = compilerProvider; } @Test public void testErrorCodePattern() { asserNotMatch(""); asserNotMatch("[]"); asserNotMatch("[a]"); asserNotMatch("[]()"); asserNotMatch("[](a)"); asserMatch("[a]()", "a", ""); asserMatch("[a](b)", "a", "b"); asserMatch("[ERROR_01](5,foobar)", "ERROR_01", "5,foobar"); } private void asserNotMatch(String test) { Matcher matcher = Message.PATTERN.matcher(test); assertFalse("Was not expecting " + Message.PATTERN + " to match " + test, matcher.matches()); } private void asserMatch(String test, String expectedCode, String expectedArguments) { Matcher matcher = Message.PATTERN.matcher(test); assertTrue("Was expecting " + Message.PATTERN + " to match " + test, matcher.matches()); assertEquals(expectedCode, matcher.group(1)); assertEquals(expectedArguments, matcher.group(2)); } @Test public void testBar() throws Exception { CompilerAssert<File, File> helper = compiler("compiler.disk").with(compilerProvider); helper.with((Provider<? extends Processor>)null); helper.assertCompile(); assertEquals(1, helper.getClassOutput().size(ReadFileSystem.FILE)); } // @javax.annotation.processing.SupportedAnnotationTypes({"*"}) @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) static class GetResource extends AbstractProcessor { /** . */ Object result = null; /** . */ final StandardLocation location; /** . */ final FileKey key; GetResource(StandardLocation location, FileKey key) { this.location = location; this.key = key; } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { try { Filer filer = processingEnv.getFiler(); FileObject o = filer.getResource(location, key.packageFQN, key.name); result = o.getCharContent(false); } catch (IOException e) { result = e; } } return false; } void assertResource(String expected) { if (result instanceof Exception) { AssertionFailedError afe = new AssertionFailedError(); afe.initCause((Throwable)result); throw afe; } else if (result instanceof CharSequence) { assertEquals(expected, result.toString()); } else { fail("Was not expecting result to be " + result); } } } @Test public void testGetResourceFromSourcePath() throws Exception { DiskFileSystem input = diskFS("compiler.getresource"); RAMFileSystem output = new RAMFileSystem(); Compiler compiler = Compiler.builder().javaCompiler(compilerProvider).sourcePath(input).output(output).build(); GetResource processor = new GetResource(StandardLocation.SOURCE_PATH, FileKey.newResourceName("compiler.getresource", "A.txt")); compiler.addAnnotationProcessor(processor); compiler.compile(); assertEquals(1, output.size(ReadFileSystem.FILE)); processor.assertResource("value"); } @Test public void testGetResourceFromClassPath() throws Exception { File fic = File.createTempFile("test", ".jar"); fic.deleteOnExit(); JavaArchive jar = ShrinkWrap.create(JavaArchive.class); jar.addAsResource(new StringAsset("the_resource"), "resource.txt"); jar.as(ZipExporter.class).exportTo(fic, true); JarFileSystem classpath = new JarFileSystem(new JarFile(fic)); RAMFileSystem output = new RAMFileSystem(); Compiler compiler = Compiler.builder(). javaCompiler(compilerProvider). config(new CompilerConfig().force(true)). addClassPath(classpath). sourcePath(new RAMFileSystem()). output(output).build(); GetResource processor = new GetResource(StandardLocation.CLASS_PATH, FileKey.newResourceName("", "resource.txt")); compiler.addAnnotationProcessor(processor); compiler.compile(); processor.assertResource("the_resource"); } // For now we don't support this until we figure the feature fully public void _testChange() throws Exception { RAMFileSystem ramFS = new RAMFileSystem(); String[] root = ramFS.getRoot(); String[] foo = ramFS.makePath(root, "foo"); String[] a = ramFS.makePath(foo, "A.java"); ramFS.updateResource(a, new Resource("package foo; public class A {}")); String[] b = ramFS.makePath(foo, "B.java"); ramFS.updateResource(b, new Resource("package foo; public class B {}")); // RAMFileSystem output = new RAMFileSystem(); Compiler compiler = Compiler.builder().sourcePath(ramFS).output(output).build(); compiler.compile(); assertEquals(2, output.size(ReadFileSystem.FILE)); Timestamped<Resource> aClass = output.getResource(new String[]{"foo", "A"}); assertNotNull(aClass); Timestamped<Resource> bClass = output.getResource(new String[]{"foo", "B"}); assertNotNull(bClass); // while (true) { ramFS.updateResource(b, new Resource("package foo; public class B extends A {}")); if (bClass.getTime() < ramFS.getLastModified(b)) { break; } else { Thread.sleep(1); } } // compiler.compile(); assertEquals(1, output.size(ReadFileSystem.FILE)); bClass = output.getResource(new String[]{"foo", "B"}); assertNotNull(bClass); } @javax.annotation.processing.SupportedAnnotationTypes({"*"}) @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) public static class ProcessorImpl extends AbstractProcessor { /** . */ final List<String> names = new ArrayList<String>(); /** . */ private boolean done; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element elt : roundEnv.getRootElements()) { if (elt instanceof TypeElement) { TypeElement typeElt = (TypeElement)elt; names.add(typeElt.getQualifiedName().toString()); } } // if (!done) { try { Filer filer = processingEnv.getFiler(); JavaFileObject b = filer.createSourceFile("compiler.processor.B"); PrintWriter writer = new PrintWriter(b.openWriter()); writer.println("package compiler.processor; public class B { }"); writer.close(); done = true; } catch (IOException e) { e.printStackTrace(); } } // return false; } } @Test public void testProcessor() throws Exception { ProcessorImpl processor = new ProcessorImpl(); CompilerAssert<File, File> compiler = compiler("compiler.processor").with(compilerProvider).with(processor); compiler.assertCompile(); assertEquals(2, compiler.getClassOutput().size(ReadFileSystem.FILE)); assertEquals(Arrays.asList("compiler.processor.A", "compiler.processor.B"), processor.names); assertEquals(1, compiler.getSourceOutput().size(ReadFileSystem.FILE)); } @Test public void testCompilationFailure() throws Exception { CompilerAssert<?, ?> compiler = compiler("compiler.failure"); assertEquals(1, compiler.failCompile().size()); } @Test public void testProcessorErrorOnElement() throws Exception { DiskFileSystem fs = diskFS("compiler.annotationexception"); Compiler compiler = Compiler.builder().javaCompiler(compilerProvider).sourcePath(fs).output(new RAMFileSystem()).build(); @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) @javax.annotation.processing.SupportedAnnotationTypes({"*"}) class Processor1 extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Deprecated.class); if (elements.size() == 1) { Element elt = elements.iterator().next(); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "the_message", elt); } return false; } } compiler.addAnnotationProcessor(new Processor1()); try { compiler.compile(); fail(); } catch (CompilationException e) { List<CompilationError> errors = e.getErrors(); assertEquals(1, errors.size()); CompilationError error = errors.get(0); assertEquals(null, error.getCode()); assertEquals(Collections.<String>emptyList(), error.getArguments()); assertEquals(fs.getPath("compiler", "annotationexception", "A.java"), error.getSourceFile()); assertTrue(error.getMessage().contains("the_message")); assertNotNull(error.getSourceFile()); assertNotNull(error.getLocation()); String absolutePath = error.getSourceFile().getAbsolutePath(); char separator = File.separatorChar; String[] absoluteNames = Tools.split(absolutePath, separator); assertTrue("Was expecting " + absolutePath + " to have at least three names ", absoluteNames.length > 3); assertEquals( "Was expecting " + absolutePath + " to end with compiler/annotationexceptions/A.java", Arrays.asList("compiler", "annotationexception", "A.java"), Arrays.asList(absoluteNames).subList(absoluteNames.length - 3, absoluteNames.length)); } } @Test public void testProcessorError() throws Exception { // Works only with javac if (compilerProvider == JavaCompilerProvider.JAVAC) { DiskFileSystem fs = diskFS("compiler.annotationexception"); Compiler compiler = Compiler.builder().javaCompiler(compilerProvider).sourcePath(fs).output(new RAMFileSystem()).build(); @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) @javax.annotation.processing.SupportedAnnotationTypes({"*"}) class Processor2 extends AbstractProcessor { boolean failed = false; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (!failed) { failed = true; processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "the_message"); } return false; } } compiler.addAnnotationProcessor(new Processor2()); try { compiler.compile(); } catch (CompilationException e) { List<CompilationError> errors = e.getErrors(); assertEquals(1, errors.size()); CompilationError error = errors.get(0); assertEquals(null, error.getCode()); assertEquals(Collections.<String>emptyList(), error.getArguments()); assertEquals(null, error.getSource()); assertTrue(error.getMessage().contains("the_message")); assertNull(error.getSourceFile()); assertNull(error.getLocation()); } } } @Test public void testErrorCode() throws IOException { final MessageCode code = new MessageCode("ERROR_01", "The error"); class P extends BaseProcessor { @Override protected void doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws ProcessingException { if (roundEnv.processingOver()) { throw new ProcessingException(code, 5, "foobar"); } } } DiskFileSystem fs = diskFS("compiler.errorcode"); Compiler compiler = Compiler. builder(). javaCompiler(compilerProvider). config(new CompilerConfig().withProcessorOption("juzu.error_reporting", "formal")). sourcePath(fs). output(new RAMFileSystem()). build(); P processor = new P(); compiler.addAnnotationProcessor(processor); try { compiler.compile(); } catch (CompilationException e) { List<CompilationError> errors = e.getErrors(); assertEquals(1, errors.size()); CompilationError error = errors.get(0); assertEquals(code, error.getCode()); assertEquals(Arrays.asList("5", "foobar"), error.getArguments()); } } @Test public void testDelete() throws Exception { DeleteResourceProcessor processor = new DeleteResourceProcessor(); CompilerAssert<File, File> compiler = compiler("compiler.deleteresource").with(compilerProvider).with(processor); File foo = compiler.getClassOutput().makePath(compiler.getClassOutput().getRoot(), "foo.txt"); Tools.write("foo", foo); compiler.assertCompile(); assertTrue(processor.done); boolean expected = compilerProvider == JavaCompilerProvider.ECJ; assertEquals(expected, processor.deleted); assertEquals(!expected, foo.exists()); } @javax.annotation.processing.SupportedAnnotationTypes({"*"}) @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) public static class DeleteResourceProcessor extends AbstractProcessor { /** . */ private boolean done; /** . */ private boolean deleted; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (!done) { try { Filer filer = processingEnv.getFiler(); FileObject foo = filer.getResource(StandardLocation.CLASS_OUTPUT, "", "foo.txt"); deleted = foo.delete(); done = true; } catch (IOException e) { e.printStackTrace(); } } // return true; } } @Test public void testIncremental() throws IOException, CompilationException { CompilerAssert<File, File> compiler = compiler(true, Name.parse("compiler.incremental"), ""). with(compilerProvider). with((Provider<? extends Processor>)null); compiler.assertCompile(); // ReadWriteFileSystem<File> classOutput = compiler.getClassOutput(); assertEquals(1, classOutput.size(ReadFileSystem.FILE)); // ReadWriteFileSystem<File> sourcePath = (ReadWriteFileSystem<File>)compiler.getSourcePath(); File b = sourcePath.makePath(sourcePath.getPath("compiler", "incremental"), "B.java"); sourcePath.updateResource(b, new Resource("package compiler.incremental; public class B extends A {}")); compiler.assertCompile(); assertEquals(2, classOutput.size(ReadFileSystem.FILE)); } @javax.annotation.processing.SupportedAnnotationTypes({"*"}) @javax.annotation.processing.SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_6) public static class ReadResource extends AbstractProcessor { /** . */ private final StandardLocation location; /** . */ private ProcessingContext processingContext; public ReadResource(StandardLocation location) { this.location = location; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.processingContext = new ProcessingContext(processingEnv); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { return _process(annotations, roundEnv); } catch (IOException e) { throw failure(e); } } private boolean _process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws IOException { if (roundEnv.processingOver()) { // Read an existing resource FileObject foo = processingContext.getResource(location, "", "foo.txt"); assertNotNull(foo); String s = Tools.read(foo.openInputStream()); assertEquals("foo_value", s); // Now we overwrite the resource foo = processingContext.createResource(location, "", "foo.txt"); OutputStream out = foo.openOutputStream(); out.write("new_foo_value".getBytes()); out.close(); // Read an non existing resource // JDK 6 strange behavior / bug happens here, we should get bar=null but we don't // JDK 7 should return null FileObject bar = processingContext.getResource(location, "", "bar.txt"); assertNull(bar); // Now create new resource foo = processingContext.createResource(location, "", "juu.txt"); out = foo.openOutputStream(); out.write("juu_value".getBytes()); out.close(); } return true; } } @Test public void testSourceOutputResource() throws IOException, CompilationException { testResource(StandardLocation.SOURCE_OUTPUT); } @Test public void testClassOutputResource() throws IOException, CompilationException { testResource(StandardLocation.CLASS_OUTPUT); } private void testResource(StandardLocation location) throws IOException, CompilationException { CompilerAssert<File, File> compiler = compiler("compiler.missingresource").with(compilerProvider).with(new ReadResource(location)); ReadWriteFileSystem<File> output; switch (location) { case SOURCE_OUTPUT: output = compiler.getSourceOutput(); break; case CLASS_OUTPUT: output = compiler.getClassOutput(); break; default: throw failure("was not expecting " + location); } // File foo = output.makePath(output.getRoot(), "foo.txt"); output.updateResource(foo, new Resource("foo_value")); // compiler.assertCompile(); // File root = output.getRoot(); Map<String, File> children = new HashMap<String, File>(); for (Iterator<File> i = output.getChildren(root);i.hasNext();) { File path = i.next(); if (output.isFile(path)) { children.put(output.getName(path), path); } } assertEquals(2, children.size()); foo = children.get("foo.txt"); assertEquals("new_foo_value", output.getResource(foo).getObject().getCharSequence(Charset.defaultCharset())); File juu = children.get("juu.txt"); assertEquals("juu_value", output.getResource(juu).getObject().getCharSequence(Charset.defaultCharset()).toString()); } @Test public void testAnnotationState() { CaptureAnnotationProcessor processor = new CaptureAnnotationProcessor().with(StringArray.class); compiler("compiler.annotationstate.multivalued").with(compilerProvider).with(processor).assertCompile(); // AnnotationState m1 = processor.get(ElementHandle.Method.create("compiler.annotationstate.multivalued.A", "m1"), StringArray.class); assertTrue(m1.isUndeclared("value")); List<?> value = assertInstanceOf(List.class, m1.resolve("value")); assertNull(m1.get("value")); assertEquals(Collections.emptyList(), value); // AnnotationState m2 = processor.get(ElementHandle.Method.create("compiler.annotationstate.multivalued.A", "m2"), StringArray.class); assertTrue(m2.isDeclared("value")); value = assertInstanceOf(List.class, m2.resolve("value")); assertSame(value, m2.get("value")); assertEquals(Collections.emptyList(), value); // AnnotationState m3 = processor.get(ElementHandle.Method.create("compiler.annotationstate.multivalued.A", "m3"), StringArray.class); assertTrue(m3.isDeclared("value")); value = assertInstanceOf(List.class, m3.resolve("value")); assertSame(value, m3.get("value")); assertEquals(Arrays.asList("warning_value"), value); // AnnotationState m4 = processor.get(ElementHandle.Method.create("compiler.annotationstate.multivalued.A", "m4"), StringArray.class); assertTrue(m4.isDeclared("value")); value = assertInstanceOf(List.class, m4.resolve("value")); assertSame(value, m4.get("value")); assertEquals(Arrays.asList("warning_value"), value); // AnnotationState m5 = processor.get(ElementHandle.Method.create("compiler.annotationstate.multivalued.A", "m5"), StringArray.class); assertTrue(m5.isDeclared("value")); value = assertInstanceOf(List.class, m5.resolve("value")); assertSame(value, m5.get("value")); assertEquals(Arrays.asList("warning_value_1", "warning_value_2"), value); } @Test public void testDot() throws Exception { CompilerAssert<File, File> compiler = compiler("compiler.dot").with(compilerProvider); compiler.with(new AbstractProcessor() { int count = 0; @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton("*"); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (count++ == 0) { try { ProcessingContext ctx = new ProcessingContext(processingEnv); ElementHandle.Package pkg = ElementHandle.Package.create(ctx.getPackageElement("compiler.dot.foo")); FileObject file = ctx.resolveResourceFromSourcePath(pkg, FileKey.newName("compiler.dot.foo", "a.b.txt")); InputStream in = file.openInputStream(); FileObject o = ctx.createResource(StandardLocation.CLASS_OUTPUT, FileKey.newName("compiler.dot.foo", "a.b.css")); OutputStream out = o.openOutputStream(); Tools.copy(in, out); Tools.safeClose(in); Tools.safeClose(out); } catch (Exception e) { throw failure(e); } } return true; } }); compiler.assertCompile(); ReadWriteFileSystem<File> classOutput = compiler.getClassOutput(); File f = new File(classOutput.getRoot(), "compiler/dot/foo/a.b.css"); InputStream in = new FileInputStream(f); String content = Tools.read(in); in.close(); assertEquals("content", content.trim()); } }