package tests;
import javax.tools.*;
import java.io.*;
import java.util.*;
import static org.junit.Assert.*;
import checkers.quals.*;
import checkers.javari.quals.*;
/**
* Abstract class for testing a checker framework.
*/
abstract public class CheckerTest {
/** The fully-qualified class name of the checker to use for tests. */
protected final String checkerName;
/** The relative path to the directory containing test inputs. */
protected final String checkerDir;
/** Extra options to pass to javac when running the checker. */
protected final String[] checkerOptions;
/**
* Creates a new checker test.
*
* @param checkerName the fully-qualified class name of the checker to use
* @param checkerDir the path to the directory of test inputs
* @param checkerOptions options to pass to the compiler when running tests
*/
public CheckerTest(String checkerName, String checkerDir, String... checkerOptions) {
this.checkerName = checkerName;
this.checkerDir = "tests" + File.separator + checkerDir;
this.checkerOptions = Arrays.copyOf(checkerOptions, checkerOptions.length);
}
/**
* Runs a test. The method uses reflection to
* determine the expected output and Java source files that would
* otherwise be passed to {@link #runTest} -- if the calling method is
* named testZZZ, this method uses an expected outfile called "ZZZ.out"
* and a Java source file called "ZZZ.java".
*/
protected void test(File testFile) {
final String expectedFileName = testFile.getPath().replace(".java", ".out");
File expectedFile = new File(expectedFileName);
runTest(expectedFile, testFile);
}
protected void test() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
assert stack.length >= 3;
String method = stack[2].getMethodName();
if (!method.startsWith("test"))
throw new AssertionError("caller's name is invalid");
String[] parts = method.split("test");
String testName = parts[parts.length - 1];
test(new File(this.checkerDir + File.separator + testName + ".java"));
}
/**
* Compiles and returns a TestRun.
*/
protected TestRun getTest(String... files) {
List<String> fileStrings = new LinkedList<String>();
for (String s : files)
fileStrings.add(checkerDir + File.separator + s);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager
= compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> tests
= fileManager.getJavaFileObjectsFromStrings(fileStrings);
// files need to compile cleanly without any errors
TestRun pureCompilation = TestInput.compileAndCheck(tests, null, new String[]{});
if (!pureCompilation.getResult()) {
String message = "Java file is not valid Java code: " + fileStrings;
System.err.println(message);
for (Diagnostic<?> d : pureCompilation) {
System.err.println(d);
}
throw new IllegalArgumentException(message);
}
return TestInput.compileAndCheck(tests, checkerName, checkerOptions);
}
/**
* Compiles and returns a TestRun.
*/
protected TestRun getTest(File... files) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager
= compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> tests
= fileManager.getJavaFileObjects(files);
return TestInput.compileAndCheck(tests, checkerName, checkerOptions);
}
/**
* Tests that the result of compiling the javaFile matches the expectedFile.
*
* @param expectedFile the expected result for compilation
* @param javaFiles the Java files to be compiled
*/
protected void runTest(File expectedFile, File ... javaFiles) {
TestRun run = getTest(javaFiles);
if (expectedFile.exists()) {
checkTestResult(run, expectedFile, TestUtilities.shouldSucceed(expectedFile), joinPrefixed(javaFiles, " ", this.checkerDir + File.separator));
} else {
List<String> expectedErrors = TestUtilities.expectedDiagnostics(this.checkerDir + File.separator, javaFiles);
checkTestResult(run, expectedErrors, expectedErrors.isEmpty(), joinPrefixed(javaFiles, " ", this.checkerDir + File.separator));
}
}
protected void runTest(List<String> expectedErrors, boolean shouldSucceed, String ... javaFiles) {
TestRun run = getTest(javaFiles);
checkTestResult(run, expectedErrors, shouldSucceed, joinPrefixed(javaFiles, " ", this.checkerDir + File.separator));
}
/**
* Tests that the result of compiling the javaFile matches the expectedFile.
*
* @param expectedFileName the expected result for compilation
* @param shouldSucceed whether the javaFile should compile successfully
* @param javaFiles the Java files to be compiled
*/
protected void runTest(String expectedFileName, boolean shouldSucceed, File ... javaFiles) {
String expectedPath = this.checkerDir + File.separator + expectedFileName;
File expectedFile = new File(expectedPath);
runTest(expectedFile, shouldSucceed, javaFiles);
}
protected void runTest(File expectedFile, boolean shouldSucceed, File ...javaFiles) {
TestRun run = getTest(javaFiles);
checkTestResult(run, expectedFile, shouldSucceed, join(javaFiles, " "));
}
protected void checkTestResult(TestRun run, File expectedFile, boolean shouldSucceed, String javaFile) {
if (shouldSucceed)
assertSuccess(run);
else
assertFailure(run);
if ((!shouldSucceed) && !(expectedFile.exists())) {
throw new Error("Did not find expected file " + expectedFile);
}
if (shouldSucceed && !(expectedFile.exists())) {
return;
}
List<Diagnostic<? extends JavaFileObject>> list = run.getDiagnostics();
assertDiagnostics(list, expectedFile, javaFile);
}
protected void checkTestResult(TestRun run, List<String> expectedErrors, boolean shouldSucceed, String javaFile) {
if (shouldSucceed)
assertSuccess(run);
else
assertFailure(run);
if (shouldSucceed && expectedErrors.isEmpty())
return;
List<Diagnostic<? extends JavaFileObject>> list = run.getDiagnostics();
assertDiagnostics(list, expectedErrors, javaFile);
}
/**
* Asserts that the test compilation completed without failures or
* exceptions.
*
* @param run the test run to check
*/
protected void assertSuccess(/*@ReadOnly*/ TestRun run) {
assertTrue("failures:\n" + run.getDiagnostics(), run.getResult());
}
/**
* Asserts that the test compilation did not complete successfully.
*
* @param run the test run to check
*/
protected void assertFailure(/*@ReadOnly*/ TestRun run) {
assertFalse("The test run was expected to issue errors/warnings, but it did not.", run.getResult());
}
/**
* Compares the result of the compiler against a list of errors in a file.
* If the file is not found or cannot be read, the assertion fails.
*
* @param actualDiagnostics the list of diagnostics from the compiler
* @param expectedDiagnosticFile a file containing a list of expected errors, one
* per line
*/
protected void assertDiagnostics(/*@ReadOnly*/ List</*@ReadOnly*/ Diagnostic<? extends JavaFileObject>> actualDiagnostics,
/*@ReadOnly*/ File expectedDiagnosticFile,
String javaFile) {
try {
BufferedReader reader = new BufferedReader(new FileReader(expectedDiagnosticFile));
ArrayList<String> lines = new ArrayList<String>();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#"))
continue;
int colonIndex = line.indexOf(':');
if (colonIndex != -1) {
lines.add(line.substring(colonIndex).trim());
} else {
// Either other javac output should be redirected
// elsewhere, so as not to confuse assertDiagnostics,
// or else assertDiagnostics ought to recognize other
// javac output. And the file format should be defined
// somewhere -- what is expected to precede the first
// colon? Should it always be in the first column?
}
}
assertDiagnostics(actualDiagnostics, lines, javaFile);
} catch (IOException e) {
fail(e.getMessage());
}
}
/**
* Compares the result of the compiler against an array of Strings.
*/
protected void assertDiagnostics(/*@ReadOnly*/ List</*@ReadOnly*/ Diagnostic<? extends JavaFileObject>> actual_diagnostics, List</*@ReadOnly*/ String> expected_diagnostics, String filename) {
String cs = (checkerDir == "" ? "" : checkerDir + File.separator); // "interned"
List<String> expectedList = new LinkedList<String>();
for (/*@ReadOnly*/ String sd : expected_diagnostics) expectedList.add(/* cs + */ sd);
List<String> resultsList = new LinkedList<String>();
for (/*@ReadOnly*/ Diagnostic<? extends JavaFileObject> d : actual_diagnostics) {
String result = d.toString().trim();
// suppress Xlint warnings
if (result.contains("uses unchecked or unsafe operations.") ||
result.contains("Recompile with -Xlint:unchecked for details."))
continue;
if (result.contains("\n")){
result = result.substring(0, result.indexOf('\n'));
}
result = result.substring(result.indexOf(".java:") + 5).trim();
resultsList.add(result);
}
List<String> foundList = new LinkedList<String>();
foundList.addAll(resultsList);
foundList.retainAll(expectedList);
String failMessage = foundList.size() + " out of "
+ expectedList.size() + " expected diagnostics "
+ (foundList.size() == 1 ? "was" : "were") +" found.\n";
boolean failed = false;
List<String> notFoundList = new LinkedList<String>();
notFoundList.addAll(expectedList);
notFoundList.removeAll(resultsList);
if (!notFoundList.isEmpty()) {
failed = true;
String message = notFoundList.size() == 1 ?
"1 expected diagnostic was not found:\n" :
notFoundList.size() + " expected diagnostics were not found:\n";
failMessage += "\n" + message;
for (String a : notFoundList)
failMessage += a + "\n";
}
List<String> unexpectedList = new LinkedList<String>();
unexpectedList.addAll(resultsList);
unexpectedList.removeAll(expectedList);
if (!unexpectedList.isEmpty()) {
failed = true;
String message = unexpectedList.size() == 1 ?
"1 unexpected diagnostic was found:\n" :
unexpectedList.size() + " unexpected diagnostics were found:\n";
failMessage += "\n" + message;
for (String a : unexpectedList)
failMessage += a + "\n";
}
if (failed) {
String failPrefix = "While type-checking " + filename + ":\n";
fail(failPrefix + failMessage);
}
}
// Lifted from plume.UtilMDE
/**
* Concatenate the string representations of the objects, placing the
* delimiter between them.
* @see ArraysMDE#toString(int[])
**/
public static String join(Object[] a, String delim) {
if (a.length == 0) return "";
if (a.length == 1) return String.valueOf(a[0]);
StringBuffer sb = new StringBuffer(String.valueOf(a[0]));
for (int i=1; i<a.length; i++)
sb.append(delim).append(a[i]);
return sb.toString();
}
/** Like join, but prefix each string by the given prefix. **/
public static String joinPrefixed(Object[] a, String delim, String prefix) {
if (a.length == 0) return "";
if (a.length == 1) return prefix + String.valueOf(a[0]);
StringBuffer sb = new StringBuffer(prefix + String.valueOf(a[0]));
for (int i=1; i<a.length; i++)
sb.append(delim).append(prefix).append(a[i]);
return sb.toString();
}
}