Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/main/java/com/google/testing/compile/junit/Compile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 Google, Inc.
*
* 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 com.google.testing.compile.junit;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;

/**
* Indicate the test method is a compile period test.
*
* The test class must extends {@link CompileTestCase}. The method also need annotated {@code @Test}
* and it must be public void and have one argument with type {@link RoundEnvironment}.
*
* @author Dean Xu ([email protected])
*/
@Retention(RUNTIME)
@Target(METHOD)
@Documented
public @interface Compile {
/**
* The source files to compile. Has same rule of {@link Class#getResource(String)}.
*/
String[] sources();

/**
* Supported annotations. Empty means '*'.
*/
Class<? extends Annotation>[] annotations() default {};

/**
* Supported source version.
*/
SourceVersion version() default SourceVersion.RELEASE_8;
}
101 changes: 101 additions & 0 deletions src/main/java/com/google/testing/compile/junit/CompileTestCase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (C) 2018 Google, Inc.
*
* 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 com.google.testing.compile.junit;

import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.model.FrameworkMethod;

/**
* Extends this class and use {@code @Compile} and {@code @Compiled} to do test for compilation.
*
* @see Compile
* @see Compiled
* @author Dean Xu ([email protected])
*/
@Ignore
@RunWith(CompileTestRunner.class)
public class CompileTestCase extends AbstractProcessor {
private FrameworkMethod method;
private Optional<Compile> anno;
private Throwable error;

protected Types types;
protected Elements elements;
protected Messager messager;
protected Filer filer;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
types = processingEnv.getTypeUtils();
elements = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}

public void setMethod(FrameworkMethod method) {
this.method = method;
this.anno = Optional.ofNullable(method.getAnnotation(Compile.class));
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
try {
method.invokeExplosively(this, roundEnv);
} catch (Throwable e) {
error = e;
}
return false;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return anno.map(c -> c.version()).orElse(SourceVersion.RELEASE_8);
}

@Override
public Set<String> getSupportedAnnotationTypes() {
return anno.map(co -> Arrays.stream(co.annotations())
.map(c -> c.getName())
.collect(Collectors.toSet()))
.filter(s -> !s.isEmpty())
.orElse(Collections.singleton("*"));
}

public Throwable getError() {
return error;
}
}
141 changes: 141 additions & 0 deletions src/main/java/com/google/testing/compile/junit/CompileTestRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (C) 2018 Google, Inc.
*
* 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 com.google.testing.compile.junit;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;

import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.tools.JavaFileObject;

import org.junit.Test;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;
import com.google.testing.compile.JavaFileObjects;

/**
* Runner for compilation test. It can process {@code @Compile}, {@code @Compiled} and normal junit
* test method.
*
* @see Compile
* @see Compiled
* @author Dean Xu ([email protected])
*/
public class CompileTestRunner extends BlockJUnit4ClassRunner {
public CompileTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}

@Override
protected void collectInitializationErrors(List<Throwable> errors) {
super.collectInitializationErrors(errors);
if (!CompileTestCase.class.isAssignableFrom(getTestClass().getJavaClass())) {
errors.add(new Exception("CompileTestRunner must run with CompileTestCase"));
}
}

@Override
protected void validateTestMethods(List<Throwable> errors) {
validatePublicVoidNoArgMethods(Test.class, false, errors);
}

@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
CompileTestCase ct = (CompileTestCase) test;
if (method.getMethod().isAnnotationPresent(Compile.class)) {
ct.setMethod(method);
return new Statement() {
@Override
public void evaluate() throws Throwable {
// Compile compile = AnnotationUtils.getAnnotation(method.getMethod(), Compile.class);
Compile compile = method.getMethod().getAnnotation(Compile.class);
Class<?> clz = getTestClass().getJavaClass();
Compiler.javac()
.withProcessors(ct)
.compile(Arrays.stream(compile.sources())
.map(s -> clz.getResource(s))
.map(u -> JavaFileObjects.forResource(u))
.toArray(JavaFileObject[]::new));
if (ct.getError() != null) {
throw ct.getError();
}
}
};
} else if (method.getMethod().isAnnotationPresent(Compiled.class)) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
// Compiled compiled = AnnotationUtils.getAnnotation(method.getMethod(), Compiled.class);
Compiled compiled = method.getMethod().getAnnotation(Compiled.class);
Class<?> clz = getTestClass().getJavaClass();
Compilation compilation = Compiler.javac()
.withProcessors(Arrays.stream(compiled.processors())
.map(c -> {
try {
return c.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Annotation Processor must has no-arg public constructor");
}
})
.toArray(Processor[]::new))
.withOptions(Arrays.stream(compiled.options()).toArray(Object[]::new))
.compile(Arrays.stream(compiled.sources())
.map(s -> clz.getResource(s))
.map(u -> JavaFileObjects.forResource(u))
.toArray(JavaFileObject[]::new));
method.invokeExplosively(ct, compilation);
}
};
} else {
return super.methodInvoker(method, test);
}
}

@Override
protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, boolean isStatic,
List<Throwable> errors) {
List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation);
for (FrameworkMethod method : methods) {
method.validatePublicVoid(isStatic, errors);
boolean compile = method.getMethod().isAnnotationPresent(Compile.class);
boolean compiled = method.getMethod().isAnnotationPresent(Compiled.class);
if (compile && compiled) {
errors.add(new Exception("Method " + method.getName() + " can't annotated both @Compile and @Compiled"));
} else if (compile) {
int count = method.getMethod().getParameterCount();
if (count != 1 || !method.getMethod().getParameterTypes()[0].isAssignableFrom(RoundEnvironment.class)) {
errors.add(new Exception(
"Method " + method.getName() + " must have only one param with type RoundEnvironment"));
}
} else if (compiled) {
int count = method.getMethod().getParameterCount();
if (count != 1 || !method.getMethod().getParameterTypes()[0].isAssignableFrom(Compilation.class)) {
errors.add(new Exception(
"Method " + method.getName() + " must have only one param with type Compilation"));
}
} else {
method.validatePublicVoidNoArg(isStatic, errors);
}
}
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/google/testing/compile/junit/Compiled.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 Google, Inc.
*
* 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 com.google.testing.compile.junit;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.annotation.processing.Processor;

import com.google.testing.compile.Compilation;

/**
* Indicate the test method is a compiled test.
*
* The test class must extends {@link CompileTestCase}. The method also need annotated {@code @Test}
* and it must be public void and have only one argument with type {@link Compilation}.
*
* @author Dean Xu ([email protected])
*/
@Retention(RUNTIME)
@Target(METHOD)
@Documented
public @interface Compiled {
/**
* The source files to compile. Has same rule of {@link Class#getResource(String)}.
*/
String[] sources();

/**
* Processors to use in this compile. Every class must have public no-arg constructor.
*/
Class<? extends Processor>[] processors() default {};

/**
* Options to use in this compile. Should be like "-Akey=value".
*/
String[] options() default {};
}
Loading