import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileReader; import java.io.PrintWriter; import javax.tools.JavaFileManager; import junit.framework.TestCase; import org.junit.Test; import com.sun.tools.javac.code.Source; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.main.OptionName; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.Pretty; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JavacFileManager; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; import com.sun.tools.javac.util.Pair; /** * Base class for JUnit test cases for Deterministic Parallel Java. * * IMPLEMENTATION NOTE: This class creates a new Context and a new compiler instance for * every single test. While this is a bit inefficient, the extra initialization work * is minimal, and it lets us avoid having any persistent state in the DPJTestCase object * itself (for instance, a Context field storing a single Context used by all tests). * Unfortunately, such persistent state causes a memory leak, because JUnit apparently * does not destroy TestCase objects until all the tests are run. The memory leak was * causing out of memory errors as the number of test cases got large. * * @author Jeff Overbey * @author Rob Bocchino */ public abstract class DPJTestCase extends TestCase { /** * Directory containing this test case */ protected final String dirname; /* CONSTRUCTOR */ DPJTestCase(String dirname) { this.dirname = dirname; } /* API METHODS */ /** * Compile a file. Compilation consists of parsing and analysis. We can specify * the filename to compile, the number of expected errors, and the number of * expected warnings. * * @param filename * @param nerrors * @param nwarnings * @return * @throws Throwable */ protected List<Pair<Env<AttrContext>, JCClassDecl>> compile(String filename, int nerrors, int nwarnings) throws Throwable { Context context = getContext(); JCCompilationUnit ast = parse(dirname + "/" + filename + ".java", context); return analyze(context, ast, nerrors, nwarnings); } /** * Overloaded interface to compile: nerrors = 0, nwarnings = 0 * @param filename * @return * @throws Throwable */ protected List<Pair<Env<AttrContext>, JCClassDecl>> compile(String filename) throws Throwable { return compile(filename, 0, 0); } /** * Overloaded interface to compile: nwarnings = 0 * @param filename * @return * @throws Throwable */ protected List<Pair<Env<AttrContext>, JCClassDecl>> compileExpectingErrors(String filename, int nerrors) throws Throwable { return compile(filename, nerrors, 0); } /** * Overloaded interface to compile: nerrors = 0 * @param nwarnings * @param filename * @return * @throws Throwable */ protected List<Pair<Env<AttrContext>, JCClassDecl>> compileExpectingWarnings(String filename, int nwarnings) throws Throwable { return compile(filename, 0, nwarnings); } /** * Compare given string with expected file * @param actual * * @throws Throwable */ protected void compareWithExpected(String actual, String expectedName) throws Throwable { ByteArrayOutputStream expected = new ByteArrayOutputStream(); PrintWriter p = new PrintWriter(expected); BufferedReader r = new BufferedReader(new FileReader(loadFile(expectedName))); for (String line = r.readLine(); line != null; line = r.readLine()) { p.print(line); p.println(); } p.flush(); expected.close(); assertEquals(expected.toString().trim(), actual.trim()); } /* PRIVATE HELPER FUNCTIONS */ /** * Load a file from disk * @param filename */ private File loadFile(String filename) { String curdir = new File(".").getAbsolutePath(); String target = "Compiler" + System.getProperty("file.separator"); int index = curdir.lastIndexOf(target); if (index < 0) { throw new Error("Cannot locate directory " + target + "test/dpj-programs"); } int len = index + target.length(); String dir = curdir.substring(0, len) + "test/dpj-programs/"; return new File(dir + filename); } /** * Make a new context * @return */ private Context getContext() { Context context = new Context(); JavacFileManager.preRegister(context); // Force Java 1.5 parsing and code generation Options.instance(context).put(OptionName.SOURCE, Source.JDK1_5.name); Options.instance(context).put(OptionName.TARGET, Target.JDK1_5.name); return context; } /** * Parse a file */ private JCCompilationUnit parse(String filename, Context context) { JavaCompiler comp = JavaCompiler.instance(context); JavacFileManager fileMgr = (JavacFileManager)context.get(JavaFileManager.class); JCCompilationUnit ast = comp.parse(fileMgr.getRegularFile(loadFile(filename))); assertNotNull(ast); return ast; } /** * Analyze a file. Analysis consists of attribution, effect checking, * flow analysis, and desugaring. * @param context * @param ast * @param nerrors * @param nwarnings * @return * @throws Throwable */ private List<Pair<Env<AttrContext>, JCClassDecl>> analyze( Context context, JCCompilationUnit ast, int nerrors, int nwarnings) throws Throwable { JavaCompiler comp = JavaCompiler.instance(context); comp.eraseDPJ = false; // Keep DPJ annotations so we can ensure they're there comp.enterTrees(List.of(ast)); comp.suppressErasure = true; // Turn off erasure of generics and regions List<Pair<Env<AttrContext>, JCClassDecl>> result = comp.desugar(comp.flow(comp.checkEffects(comp.attribute(comp.todo)))); context.get(JavaFileManager.class).close(); assertEquals(nerrors, Log.instance(context).nerrors); assertEquals(nwarnings, Log.instance(context).nwarnings); return result; } }