package org.jmlspecs.openjmltest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.tools.JavaFileObject;
import org.jmlspecs.openjml.JmlSpecs;
import org.jmlspecs.openjml.Strings;
import org.junit.Before;
import org.junit.BeforeClass;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
/** This is a base class for unit test files that exercise the RAC.
* It inherits from JmlTestCase the diagnostic collector implementation
* and the (optional) collection of System.out and System.err. It
* implements as well the mechanisms for running RAC via programmatic
* calls to openjml and then executing the resulting program.
*
* @author David R. Cok
*
*/
public abstract class RacBase extends JmlTestCase {
public final static String OpenJMLDemoPath = "../../OpenJMLDemo";
public final static String OpenJMLNonPublicDemoPath = "../../OpenJMLDemoNonPublic";
protected String testspecpath1 = "$A"+z+"$B";
protected String testspecpath;
protected int expectedExit = 0; // Expected result of compiler
protected int expectedRACExit = 0; // Expected result of RACed program
protected int expectedNotes; // Number of messages to ignore (e.g. uninteresting compiler warnings)
protected boolean jdkrac = false; // Set to true to do external system tests of RAC (emulating outside of JUnit)
protected boolean continueAnyway = false; // If true, attempt to run the program despite compiler warnings or errors
protected String expected_compile = "expected-compile";
protected String expected_run = "expected-run";
/** These are the default command-line arguments for running the RACed
* program. The first argument is the java executable; the null argument
* is replaced by the class name of the class containing the main method.
* */
protected String[] defrac = new String[]{jdk, "-ea", "-classpath","../OpenJML/bin"+z+"../OpenJML/bin-runtime"+z+"testdata",null};
/** These are actual command-line arguments, if they are set differently
* by a subclass.
*/
protected String[] rac; // initialized in subclasses
@BeforeClass
public static void mktempdirectory() {
File f = new File("testdata");
if (!f.exists()) {
f.mkdir();
}
}
/** Derived classes can initialize
* testspecpath1 - the specspath to use
* <BR>jdkrac - default is false; if true, adjusts the classpath???
* <BR>rac - the command-line to run the raced program; default is the contents of defrac
* <BR>
* After this call, if desired, modify
* <BR>print - if true, prints out errors as encountered, for test debugging (and disables failing the JUnit test if the error mismatches)
* <BR>expectedExit - the expected exit code from running openjml
* <BR>expectedRacExit - the expected exit code from running the RACed program
* <BR>expectedErrors - the expected number of errors from openjml (typically and by default 0)
*/
@Override
@Before
public void setUp() throws Exception {
//System.out.println("Using " + jdk);
// Use the default specs path for tests
testspecpath = testspecpath1;
// Define a new collector that filters out the notes
collector = new FilteredDiagnosticCollector<JavaFileObject>(false);
super.setUp();
// Setup the options
main.addOptions("-specspath", testspecpath);
main.addOptions("-d", "testdata"); // This is where the output program goes
main.addOptions("-rac","-racJavaChecks","-racCheckAssumptions");
if (jdkrac) {
String sy = Options.instance(context).get(Strings.eclipseProjectLocation);
if (sy == null) {
fail("The OpenJML project location should be set using -D" + Strings.eclipseProjectLocation + "=...");
} else if (!new File(sy).exists()) {
fail("The OpenJML project location set using -D" + Strings.eclipseProjectLocation + " to " + sy + " does not exist");
} else {
main.addOptions("-classpath",sy+"/testdata"+z+sy+"/jdkbin"+z+sy+"/bin");
}
}
main.addOptions("-showNotImplemented");
main.addOptions("-no-purityCheck"); // System specs have a lot of purity errors, so turn this off for now
main.addOptions("-no-internalSpecs"); // Faster with this option; should work either way
main.addOptions("-no-racShowSource");
specs = JmlSpecs.instance(context);
expectedExit = 0;
expectedRACExit = 0;
expectedNotes = 2; // Two lines to ignore
print = false;
}
@Override
public void tearDown() throws Exception {
super.tearDown();
specs = null;
}
public static String macstring = "Exception in thread \"main\" ";
/** This method does the running of a RAC test for tests that supply with body
* of a file as a String.
* No output is
* expected from running openjml to produce the RACed program;
* the number of expected diagnostics is set by 'expectedErrors'.
* @param classname The fully-qualified classname for the test class
* @param compilationUnitText the compilation unit text which will be put in a mock file
* @param list any expected diagnostics from openjml, followed by the error messages from the RACed program, line by line
*/
public void helpTCX(String classname, String compilationUnitText, Object... list) {
String term = "\n|(\r(\n)?)"; // any of the kinds of line terminators
StreamGobbler out=null,err=null;
boolean isMac = System.getProperty("os.name").contains("Mac");
try {
ListBuffer<JavaFileObject> files = new ListBuffer<JavaFileObject>();
String filename = classname.replace(".","/")+".java";
JavaFileObject f = new TestJavaFileObject(filename,compilationUnitText);
files.append(f);
for (JavaFileObject ff: mockFiles) {
if (ff.toString().endsWith(".java")) files.append(ff);
}
Log.instance(context).useSource(files.first());
int ex = main.compile(new String[]{}, null, context, files.toList(), null).exitCode;
if (print) printDiagnostics();
int observedMessages = collector.getDiagnostics().size() - expectedNotes;
if (observedMessages < 0) observedMessages = 0;
for (int i=0; i<observedMessages; i++) {
int k = 2*i + 2*expectedNotes;
if (k >= list.length) {
if (!print) printDiagnostics();
fail("More diagnostics than expected");
}
String s = noSource(collector.getDiagnostics().get(i));
if (s.startsWith(macstring) && isMac) s = s.substring(macstring.length());
assertEquals("Message " + i, list[k].toString(), s);
assertEquals("Message " + i, ((Integer)list[k+1]).intValue(), collector.getDiagnostics().get(i).getColumnNumber());
}
if (ex != expectedExit) fail("Compile ended with exit code " + ex);
if (ex != 0 && !continueAnyway) return;
if (rac == null) rac = defrac;
rac[rac.length-1] = classname;
Process p = Runtime.getRuntime().exec(rac);
out = new StreamGobbler(p.getInputStream());
err = new StreamGobbler(p.getErrorStream());
out.start();
err.start();
if (timeout(p,10000)) { // 10 second timeout
fail("Process did not complete within the timeout period");
}
int i = observedMessages*2;
if (print) {
String data = out.input();
if (data.length() > 0) {
String[] lines = data.split(term);
for (String line: lines) {
System.out.println("OUT: " + line);
}
}
data = err.input();
if (data.length() > 0) {
String[] lines = data.split(term);
for (String line: lines) {
System.out.println("ERR: " + line);
}
}
}
String data = out.input();
if (data.length() > 0) {
String[] lines = data.split(term);
for (String line: lines) {
if (i < list.length) assertEquals("Output line " + i, list[i], line);
i++;
}
}
data = err.input();
if (data.length() > 0) {
String[] lines = data.split(term);
for (String line: lines) {
if (isMac && !line.equals(list[i]) && line.startsWith(macstring)) line = line.substring(macstring.length());
if (i < list.length) assertEquals("Output line " + i, list[i], line);
i++;
}
if (isMac && i < list.length && list[i].equals(macstring)) i++;
}
if (i != list.length && !print) { // if print, then we already printed
printDiagnostics();
}
if (i> list.length) fail("More output than specified: " + i + " vs. " + list.length + " lines");
if (i< list.length) fail("Less output than specified: " + i + " vs. " + list.length + " lines");
if (p.exitValue() != expectedRACExit) fail("Exit code was " + p.exitValue());
} catch (Exception e) {
e.printStackTrace(System.out);
fail("Exception thrown while processing test: " + e);
} catch (AssertionError e) {
if (!print) printDiagnostics();
if (!print && !noExtraPrinting) {
if (out != null) {
String[] lines = out.input().split(term);
for (String line: lines) {
System.out.println("OUT: " + line);
}
}
if (err != null) {
String[] lines = err.input().split(term);
for (String line: lines) {
System.out.println("ERR: " + line);
}
}
}
throw e;
} finally {
// if (r != null)
// try { r.close(); }
// catch (java.io.IOException e) {
// // Give up if there is an exception
// }
// if (rerr != null)
// try { rerr.close(); }
// catch (java.io.IOException e) {
// // Give up if there is an exception
// }
}
}
// The following is for tests based on files
protected boolean runrac = true;
/** The command-line to use to run RACed programs - note the inclusion of the
* RAC-compiled JDK library classes ahead of the regular Java libaray classes
* in the boot class path. (This may not work on all platforms)
*/
String[] sysrac = new String[]{jdk, "-classpath","bin"+z+"../OpenJML/bin-runtime"+z+"testdata",null};
/** Call this as a setup routine, followed by RacBase.setUp for file based tests */
public void setUpForFiles() throws Exception {
rac = sysrac;
jdkrac = true;
runrac = true;
}
/** This method does the running of a RAC test that is based in an external file. No output is
* expected from running openjml to produce the RACed program;
* the number of expected diagnostics is set by 'expectedErrors'.
* @param dirname The directory containing the test sources, a relative path
* from the project folder
* @param mainClassname The fully-qualified classname for the test class (where main is)
* @param list any expected diagnostics from openjml, followed by the error messages from the RACed program, line by line
*/
public void helpTCF(String dirname, String outputdir, String mainClassname, String ... opts) {
boolean print = false;
StreamGobbler out=null,err=null;
try {
String actCompile = outputdir + "/actual-compile";
String actRun = outputdir + "/actual-run";
new File(outputdir).mkdirs();
new File(actCompile).delete();
new File(actRun).delete();
List<String> args = new LinkedList<String>();
//args.add("-no-jml");
args.add("-d");
args.add("testdata");
args.add("-rac");
args.add("-no-purityCheck");
args.add("-code-math=java");
if (new File(dirname).isDirectory()) args.add("-dir");
args.add(dirname);
args.addAll(Arrays.asList(opts));
PrintWriter pw = new PrintWriter(actCompile);
int ex = org.jmlspecs.openjml.Main.execute(pw,null,null,args.toArray(new String[args.size()]));
pw.close();
String compdiffs = "";
if (new File(outputdir + "/" + expected_compile).exists()) {
compdiffs = compareFiles(outputdir + "/" + expected_compile, actCompile);
if (compdiffs == null) {
new File(actCompile).delete();
}
} else {
for (String file: new File(outputdir).list()) {
if (!file.contains("expected-compile")) continue;
compdiffs = compareFiles(outputdir + "/" + file, actCompile);
if (compdiffs == null) {
new File(actCompile).delete();
break;
}
}
}
if (compdiffs != null) {
if (compdiffs.isEmpty()) {
fail("No expected output file for compiler output");
} else {
System.out.println(compdiffs);
// fail("Files differ: " + compdiffs);
}
}
if (ex != expectedExit) fail("Compile ended with exit code " + ex);
if (runrac) {
if (rac == null) rac = defrac;
rac[rac.length-1] = mainClassname;
Process p = Runtime.getRuntime().exec(rac);
out = new StreamGobbler(p.getInputStream());
err = new StreamGobbler(p.getErrorStream());
out.start();
err.start();
if (timeout(p,10000)) { // 10 second timeout
fail("Process did not complete within the timeout period");
}
String output = out.input().replaceAll("@[0-9abcdef]+", "@########");
ex = p.exitValue();
output = "OUT:" + eol + output + eol + "ERR:" + eol + err.input();
if (print) System.out.println(output);
String diffs = "";
for (String file: new File(outputdir).list()) {
if (!file.contains("expected-run")) continue;
diffs = compareText(outputdir + "/" + file,output);
if (diffs == null) break;
}
if (diffs != null) {
BufferedWriter b = new BufferedWriter(new FileWriter(actRun));
b.write(output);
b.close();
}
if (ex != expectedRACExit) fail("Execution ended with exit code " + ex + " " + output);
if (diffs != null) {
if (diffs.isEmpty()) {
fail("No expected output file for runtime output");
} else {
System.out.println(diffs);
fail("Unexpected output: " + diffs);
}
}
}
if (compdiffs != null) fail("Files differ: " + compdiffs);
} catch (Exception e) {
e.printStackTrace(System.out);
fail("Exception thrown while processing test: " + e);
} catch (AssertionError e) {
throw e;
} finally {
// Should close open objects
}
}
}