package org.jmlspecs.openjmltest.testcases; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.tools.JavaFileObject; import org.jmlspecs.openjml.JmlOption; import org.jmlspecs.openjml.JmlSpecs; import org.jmlspecs.openjml.JmlSpecs.Dir; import org.jmlspecs.openjmltest.TCBase; import org.jmlspecs.openjmltest.TestJavaFileObject; import org.jmlspecs.openjml.Main; import org.jmlspecs.openjml.Utils; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.ParameterizedWithNames; import org.junit.runners.Parameterized.Parameters; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Log; /** This is the parent class for classes that simply test whether the spec file * for a JDK class parses without error. There are two methods of creating * these tests implemented here. * <P> * One is to create a TestSuite, and dynamically add to it an individual test * for each class found. That construction has to be done statically. It has * the advantage that each test appears in the JUnit list of tests and marked * as successful or not. The individual tests can be rerun from the JUnit * test runner, but the suite as a whole cannot. The suite can be run as a * Run Configuration. Another advantage is that the tests can * be canceled while in progress. * <P> * A second implementation, currently disabled, has SpecsBase consisting of * just one test, that loops through all the classes being tested. A disadvantage * is that one cannot cancel the tests while in progress and they do not appear * in the JUnit listing. They can also be run from the RunConfiguration. To * enable this mode, comment out the suite() and runTest() methods. * * <P> * Alternatively, you can create explicit tests for individual system classes. * The template is the following: * *<PRE> public void testFile() { checkClass("<fully-qualified-type-name>"); } </PRE> * or *<PRE> public void testFile() { helpTCF("A.java","public class A { <fully-qualified-type-name> f; }" ); } </PRE> * *For generic classes (with one type argument) write *<PRE> public void testFile() { checkClassGeneric("<fully-qualified-type-name>"); } </PRE> * or *<PRE> public void testFile() { helpTCF("A.java","public class A { <fully-qualified-type-name><?> f; }" ); } </PRE> * * Note also that no errors are reported if there is no specification file or * the class path is such that the spec file is not found. * * @author David Cok * */ // Note - this does not test spec files that are hidden by a later version // you need to rerun Eclipse with a different JDK and correspondingly different // specifications path. You can do this with separate Run Configurations. // At one point in development, running these tests would cause later tests in // the JUnit sequence to fail, when they would not fail otherwise. Before that // problem could be solved, it disappeared, so its cause and resolution are // unknown. For now we will leave these tests in, but beware that this was once // the case and may crop up again in the future. // Since these tests are a bit time-consuming (about 2 min right now) and will be // more so as more spec files are added, you can turn them off with the dotests // flag. @RunWith(ParameterizedWithNames.class) public class SpecsBase extends TCBase { /** Enables or disables this suite of tests */ static private boolean dotests = true; // Change this to enable/disable dynamic tests /** If true, then a progress message is printed as each test is executed.*/ private static boolean verbose = false; @Parameters static public Collection<String[]> datax() { if (!dotests) return new ArrayList<String[]>(0); Collection<String[]> data = new ArrayList<String[]>(1000); for (String f: findAllFiles(null)) { data.add(new String[]{ f}); } return data; } /** The name of the class to be tested (which is also the name of the test) * when the suite mode is used. This is defined simply to enable debugging. */ /*@ non_null*/ private String classname; /** We use SpecsBase as a test case, with a name and its own runTest, to * execute the test on a given class name. * @param classname the fully qualified class to test */ public SpecsBase(String classname) { this.classname = classname; } @Override public void setUp() throws Exception { useSystemSpecs = true; super.setUp(); // We turn off purity checking because there are too many purity errors in the specs to handle right now. (TODO) JmlOption.setOption(context,JmlOption.PURITYCHECK,false); expectedExit = -1; // -1 means use default: some message==>1, no messages=>0 // this needs to be set manually if all the messages are warnings print = false; // true = various debugging output } /** Set to true if errors are found in any test in checkFiles */ protected boolean foundErrors; /** Helper method that executes a test * * @param filename name to use for the pseudo source file * @param s the code for the pseudo source file * @param testClass class being tested, for output only */ //@ modifies foundErrors; public void helpTCFile(String filename, String s, String testClass) { try { JavaFileObject f = new TestJavaFileObject(filename,s); if (filename != null) addMockFile("#B/" + filename,f); Log.instance(context).useSource(f); List<JavaFileObject> files = List.of(f); int ex = main.compile(new String[]{}, null, context, files, null).exitCode; if (print) JmlSpecs.instance(context).printDatabase(); int expected = expectedExit; if (expected == -1) expected = collector.getDiagnostics().size() == 0 ? 0 : 1; if (ex != expected) { System.out.println("Unexpected return code for " + testClass + " actual: " + ex + " expected: " + expected); foundErrors = true; } if (collector.getDiagnostics().size() != 0) { System.out.println("ERRORS FOUND " + testClass); foundErrors = true; printDiagnostics(); } } catch (Exception e) { e.printStackTrace(System.out); fail("Exception thrown while processing test: " + testClass + " " + e); } catch (AssertionError e) { if (!print && !noExtraPrinting) printDiagnostics(); throw e; } assertTrue("Found errors checking specs for " + testClass, !foundErrors); } /** This test tests the file that is named as classname by the constructor */ @Test public void testSpecificationFile() { foundErrors = false; int n = counts.get(classname); if (verbose) System.out.println("JUnit SpecsBase: " + classname + " " + n); if (n < typeargs.length) checkClass(classname, n); else { assertTrue("Not implemented for " + n + " + generic arguments: " + classname,false); } assertTrue("Errors found",!foundErrors); } /** Finds all classes that have library specification files. */ static public SortedSet<String> findAllFiles(/*@ nullable*/ JmlSpecs specs) { System.out.println("JRE version " + System.getProperty("java.version")); try { if (specs == null) { Main main = new Main(); Context context = main.context(); specs = JmlSpecs.instance(context); specs.setSpecsPath("$SY"); } } catch (IOException e) { e.printStackTrace(); fail("Exception in findAllFiles"); } java.util.List<Dir> dirs = specs.getSpecsPath(); assertTrue ("Null specs path",dirs != null); assertTrue ("No specs path",dirs.size() != 0); SortedSet<String> classes = new TreeSet<String>(); for (Dir dir: dirs) { File d = new File(dir.toString()); classes.addAll(findAllFiles(d, dir.toString())); } classes.removeAll(donttest); System.out.println(classes.size() + " system specification classes found"); return classes; } /** Set of classes (fully qualified, dot-separated names) that should not * be tested. */ static Set<String> donttest = new HashSet<String>(); static { donttest.add("java.lang.StringCoding"); // (FIXME) Turn this off because it is not public } static java.util.HashMap<String,Integer> counts = new java.util.HashMap<>(); /** Creates a list of all the files (of any suffix), interpreted as fully-qualified Java class * names when the root prefix is removed, * recursively found underneath the given directory * @param d the directory in which to search * @param root the prefix of the path to ignore * @return list of dot-separated class names for which files were found */ static public java.util.List<String> findAllFiles(File d, String root) { String[] files = d.list(); java.util.List<String> list = new ArrayList<String>(); if (files == null) return list; for (String s: files) { if (s.charAt(0) == '.') continue; File f = new File(d,s); if (f.isDirectory()) { list.addAll(findAllFiles(f, root)); } else { String ss = f.toString().substring(root.length()+1); int p = ss.lastIndexOf('.'); ss = ss.substring(0,p).replace(File.separatorChar,'.'); list.add(ss); try { // This is a imprecise method to count the number of type arguments, but so for I have not // found any .jml files for which it fails. If the number is wrong and non-zero, the test will fail, // but if wrong and 0 it may not. java.util.List<String> lines = java.nio.file.Files.readAllLines(f.toPath(), java.nio.charset.Charset.defaultCharset()); StringBuffer sb = new StringBuffer(); for (String line: lines) sb.append(line); String all = sb.toString(); int k = ss.lastIndexOf('.'); String tail = ss.substring(k+1); k = all.indexOf(tail + "<"); int n = 0; if (k >= 0) { n = 1; k += (tail + "<").length(); while (all.charAt(k) != '>') { if (all.charAt(k) == ',') n++; k++; } } Integer nn = counts.get(ss); if (nn == null || n > nn) counts.put(ss, n); } catch (Exception e) { assertTrue("Failed to find number of generic type arguments", false); } } } return list; } String[] typeargs = { "", "<?>", "<?,?>" }; /** Does a test on the given fully qualified, * dot-separated class name with n generic type arguments * * @param className the name of the class to test */ //@ modifies foundErrors; public void checkClass(String className, int n) { String program = "public class AJDK { "+ className + typeargs[n] +" o; }"; helpTCFile("AJDK.java",program,className); } // FIXME - the above test template does not seem to trigger all the // modifier checking in attribute testing. /** Use this to test the specs for a specific file. Enable it by * adding an @Test as an annotation. */ // @Test public void testFileTemp() { checkClass("java.util.LinkedList", 1); } }