/*
* Copyright 2016 Google Inc. All rights reserved.
*
* 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 org.inferred.freebuilder.processor.util.testing;
import org.inferred.freebuilder.processor.util.testing.TestBuilder.TestSource;
import org.junit.Test;
import javax.annotation.processing.Processor;
import javax.tools.JavaFileObject;
/**
* Convenience class for performing behavioral tests of API-generating
* annotation processors.
*
* <h3>Behavior testing of generated APIs</h3>
*
* <p>Annotation processors that generate complex APIs from small template classes are
* difficult to write good tests for. For an example, take a processor that generates
* a builder for a class. Comparing generated source with a golden output, whether via
* exact line-by-line comparison, AST comparison or bytecode comparison, leads to fragile
* tests. Adding a new method—say, buildPartial()—or changing the way an
* unset field is represented internally—say, from a null value to an explicit
* boolean field—will break every test, even though the user-visible behavior is
* unaltered.
*
* <p>Additionally, as test code will not compile without the processor, compilation
* becomes part of the test. Moving part of the test out of JUnit loses convenient
* integration with a number of tools and ADEs.
*
* <p>Behavioral testing verifies the generated code by compiling and running a small
* Java test against it. Well-written behavioral tests will check specific contracts
* the code generator must honor—e.g. "the data object returned will contain the
* values set on the builder", or "an exception will be thrown if a field has not been
* set"—contracts that will continue to hold even as internal representations
* change, and new features are added.
*
* <p>BehaviorTester takes a set of annotation processors and Java classes, and runs
* the JVM's own compiler against them. It also takes test code, wraps it in a static
* method, compiles and invokes it. As we assume the classes and test code will not
* compile without the annotation processors, we take them in as strings, which may
* be hard-coded in the test or read in from a resource file. Here is an example for
* a hypothetical Builder generator:
*
* <blockquote><code><pre>
* {@link BehaviorTester#create()}
* {@link #with(Processor) .with}(builderGeneratingProcessor)
* {@link #with(JavaFileObject) .with}(new {@link SourceBuilder}()
* .addLine("package com.example;")
* .addLine("{@literal @}%s public TestClass { ", MakeMeABuilder.class)
* .addLine(" private final %s<%s> strings;", List.class, String.class)
* .addLine(" TestClass(TestClassBuilder builder) {")
* .addLine(" strings = builder.getStrings();")
* .addLine(" }")
* .addLine(" public %s<%s> getStrings() {", List.class, String.class)
* .addLine(" return strings;")
* .addLine(" }")
* .addLine("}")
* .build())
* {@link #with(JavaFileObject) .with}(new {@link TestBuilder}()
* .addLine("com.example.TestClass instance = new com.example.TestClassBuilder()")
* .addLine(" .addString(\"Foo\")")
* .addLine(" .addString(\"Bar\")")
* .addLine(" .build();")
* .addLine("assertEquals(\"Foo\", instance.getStrings().get(0));")
* .addLine("assertEquals(\"Bar\", instance.getStrings().get(1));")
* .build())
* {@link #runTest()};
* </pre></code></blockquote>
*/
public interface BehaviorTester {
static SingleBehaviorTester create() {
return new SingleBehaviorTester();
}
/** Adds a {@link Processor} to pass to the compiler when {@link #runTest} is invoked. */
BehaviorTester with(Processor processor);
/**
* Adds a {@link JavaFileObject} to pass to the compiler when {@link #runTest} is invoked.
*
* @see SourceBuilder
* @see TestBuilder
*/
BehaviorTester with(JavaFileObject compilationUnit);
/**
* Adds a {@link TestSource} to compile and execute when {@link #runTest} is invoked.
*/
BehaviorTester with(TestSource testSource);
/**
* Ensures {@link Thread#getContextClassLoader()} will return a class loader containing the
* compiled sources. This is needed by some frameworks, e.g. GWT, but requires us to run tests
* on a separate thread, which complicates exceptions and stack traces.
*/
BehaviorTester withContextClassLoader();
/**
* Compiles everything given to {@link #with}.
*
* @return a {@link CompilationSubject} with which to make further assertions
*/
CompilationSubject compiles();
/**
* Compiles, loads and tests everything given to {@link #with}.
*
* <p>Runs the compiler with the provided sources and processors. Loads the generated code into a
* classloader. Finds all {@link Test @Test}-annotated methods (e.g. those built by {@link
* TestBuilder}) and invokes them. Aggregates all exceptions, and propagates them to the caller.
*/
default void runTest() {
compiles().allTestsPass();
}
/**
* Assertions that can be made about a compilation run.
*/
public interface CompilationSubject {
/**
* Fails if the compiler issued warnings.
*/
CompilationSubject withNoWarnings();
/**
* Loads and tests all test sources.
*
* <p>Aggregates all exceptions, and propagates them to the caller.
*/
CompilationSubject allTestsPass();
/**
* Loads and tests all {@code testSources}.
*
* <p>Aggregates all exceptions, and propagates them to the caller. All test sources must have
* been passed to {@link BehaviorTester#with(TestSource)} prior to calling
* {@link BehaviorTester#compiles()}.
*/
CompilationSubject testsPass(
Iterable<? extends TestSource> testSources,
boolean shouldSetContextClassLoader);
}
}