001 // This file is part of the program FRYSK. 002 // 003 // Copyright 2005, 2006, 2007, 2008, Red Hat Inc. 004 // 005 // FRYSK is free software; you can redistribute it and/or modify it 006 // under the terms of the GNU General Public License as published by 007 // the Free Software Foundation; version 2 of the License. 008 // 009 // FRYSK is distributed in the hope that it will be useful, but 010 // WITHOUT ANY WARRANTY; without even the implied warranty of 011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 // General Public License for more details. 013 // 014 // You should have received a copy of the GNU General Public License 015 // along with FRYSK; if not, write to the Free Software Foundation, 016 // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 017 // 018 // In addition, as a special exception, Red Hat, Inc. gives You the 019 // additional right to link the code of FRYSK with code not covered 020 // under the GNU General Public License ("Non-GPL Code") and to 021 // distribute linked combinations including the two, subject to the 022 // limitations in this paragraph. Non-GPL Code permitted under this 023 // exception must only link to the code of FRYSK through those well 024 // defined interfaces identified in the file named EXCEPTION found in 025 // the source code files (the "Approved Interfaces"). The files of 026 // Non-GPL Code may instantiate templates or use macros or inline 027 // functions from the Approved Interfaces without causing the 028 // resulting work to be covered by the GNU General Public 029 // License. Only Red Hat, Inc. may make changes or additions to the 030 // list of Approved Interfaces. You must obey the GNU General Public 031 // License in all respects for all of the FRYSK code and other code 032 // used in conjunction with FRYSK except the Non-GPL Code covered by 033 // this exception. If you modify this file, you may extend this 034 // exception to your version of the file, but you are not obligated to 035 // do so. If you do not wish to provide this exception without 036 // modification, you must delete this exception statement from your 037 // version and license this file solely under the GPL without 038 // exception. 039 040 package frysk.junit; 041 042 import frysk.config.FryskVersion; 043 import frysk.config.Prefix; 044 import frysk.config.Host; 045 import frysk.rsl.Log; 046 import frysk.rsl.LogOption; 047 import frysk.expunit.Expect; 048 import gnu.classpath.tools.getopt.FileArgumentCallback; 049 import gnu.classpath.tools.getopt.Option; 050 import gnu.classpath.tools.getopt.OptionException; 051 import gnu.classpath.tools.getopt.Parser; 052 import java.util.ArrayList; 053 import java.util.Collection; 054 import java.util.Enumeration; 055 import java.util.Iterator; 056 import java.util.LinkedList; 057 import java.util.regex.PatternSyntaxException; 058 import junit.framework.Test; 059 import junit.framework.TestResult; 060 import junit.framework.TestSuite; 061 import junit.textui.TestRunner; 062 063 /** 064 * <em>frysk</em> specific extension to the JUnit test framework. 065 */ 066 067 public class Runner extends TestRunner { 068 private static final Log fine = Log.fine(Runner.class); 069 070 // Repeat once by default. 071 private int repeatValue = 1; 072 private Collection testCases = null; 073 private boolean listClassesOnly = false; 074 // Put all tests through a filter; by default exclude all Stress.* 075 // classes. 076 private String testFilter = "^(|.*\\.)(?!Stress)[^\\.]*$"; 077 private ArrayList excludeTests = new ArrayList(); 078 private ArrayList includeTests = new ArrayList(); 079 080 private LinkedList otherArgs; 081 082 public static void usage(String message, int exitVal) { 083 System.out.println (message); 084 System.exit (exitVal); 085 } 086 087 public void setTestCases(Collection testCases) { 088 this.testCases = testCases; 089 } 090 091 public Collection getTestCases() { 092 return this.testCases; 093 } 094 095 private int runCases(Collection testClasses) { 096 // Create the testsuite to be run, either as specified on the 097 // command line, or from the provided list of classes. 098 099 TestSuite testSuite = new TestSuite (); 100 101 // Construct the testsuite from the list of names. 102 for (Iterator i = otherArgs.iterator(); i.hasNext(); ) { 103 String arg = (String) i.next(); 104 105 // A -N argument specifying a repeat count. 106 if (arg.matches("^-[0-9]*$")) { 107 repeatValue = - Integer.parseInt(arg); 108 fine.log(this, "repeat", repeatValue); 109 continue; 110 } 111 112 String testMethod; 113 String testClass; 114 if (arg.matches("^test.*\\(.*\\)$")) { 115 // testFoo(frysk.class) 116 String[] testTuple = arg.split("[\\(\\)]"); 117 testMethod = testTuple[0]; 118 testClass = testTuple[1]; 119 } else if (arg.matches(".*\\.test[A-Z][^.]*")) { 120 // frysk.class.testMethod 121 int lidot = arg.lastIndexOf('.'); 122 testClass = arg.substring(0, lidot); 123 testMethod = arg.substring(lidot + 1); 124 } else { 125 // frysk.class 126 testClass = arg; 127 testMethod = null; 128 } 129 fine.log(this, "testClass", testClass, "testMethod", testMethod); 130 131 for (Iterator c = testClasses.iterator(); c.hasNext (); ) { 132 Class testKlass = (Class) c.next (); 133 if (testMethod == null) { 134 // class only 135 if (testKlass.getName().startsWith(testClass)) { 136 testSuite.addTest(new TestSuite(testKlass)); 137 fine.log(this, "adding", testKlass); 138 } 139 } else { 140 // class.name or name(class) 141 if (testKlass.getName().equals(testClass)) { 142 try { 143 // Probe the class to see if the method 144 // exists. 145 testKlass.getMethod(testMethod, null); 146 TestCase test = (TestCase)testKlass.newInstance(); 147 test.setName(testMethod); 148 testSuite.addTest(test); 149 fine.log(this, "adding", testKlass); 150 } catch (NoSuchMethodException e) { 151 System.out.println("Couldn't find test method: " + testClass + "." + testMethod); 152 } catch (InstantiationException e) { 153 System.out.println("Couldn't instantiate class with name: " 154 + testClass); 155 } catch (IllegalAccessException e) { 156 System.out.println("Couldn't access class with name: " 157 + testClass); 158 } 159 } 160 } 161 } 162 } 163 164 if (otherArgs.size() == 0) { 165 for (Iterator i = testClasses.iterator (); i.hasNext (); ) { 166 Class testKlass = (Class) i.next (); 167 // Only include tests that gets by both filters. 168 if (testKlass.getName ().matches (testFilter)) { 169 boolean addit = true; 170 for (int j = 0; j < excludeTests.size(); j++) { 171 try { 172 if (testKlass.getName () 173 .matches ((String)excludeTests.get (j))) { 174 addit = false; 175 break; 176 } 177 } catch (PatternSyntaxException p) { 178 System.out.println(p.getMessage()); 179 } 180 } 181 if (!addit) { 182 for (int j = 0; j < includeTests.size(); j++) { 183 try { 184 if (testKlass.getName () 185 .matches ((String)includeTests.get (j))) { 186 addit = true; 187 break; 188 } 189 } catch (PatternSyntaxException p) { 190 System.out.println(p.getMessage()); 191 } 192 } 193 } 194 if (addit) { 195 testSuite.addTest (new TestSuite (testKlass)); 196 } else { 197 System.out.println ("Omitting " + testKlass.getName()); 198 } 199 } 200 } 201 } 202 203 if (listClassesOnly) { 204 for (Enumeration e = testSuite.tests (); e.hasMoreElements (); ) { 205 Test test = (Test) e.nextElement (); 206 System.out.println (test.toString ()); 207 } 208 return SUCCESS_EXIT; 209 } 210 211 // Run the TestSuite <<repeat>> times. 212 try { 213 for (int i = 0; i < this.repeatValue; i++) { 214 TestResult testResult = doRun (testSuite); 215 216 if (!testResult.wasSuccessful()) { 217 System.out.println("Failed after run #" + i); 218 return FAILURE_EXIT; 219 } 220 } 221 } catch(Exception e) { 222 System.err.println(e.getMessage()); 223 return EXCEPTION_EXIT; 224 } 225 return SUCCESS_EXIT; 226 } 227 228 /** 229 * Create and return the command line parser used by frysk's JUnit 230 * tests. 231 */ 232 private Parser createCommandLineParser (String programName) { 233 Parser parser = new Parser(programName, FryskVersion.getVersion(), 234 true); 235 236 parser.add(new LogOption("debug", 'c')); 237 238 parser.add(new Option("unbreak", 'u', "Run broken tests") { 239 public void parsed (String arg) throws OptionException { 240 skipUnresolvedTests = false; 241 } 242 }); 243 244 // Determine the number of times that the testsuite should be 245 // run. 246 parser.add (new Option ("repeat", 'r', 247 "Set the count of repeating the test.", 248 "<repeat-count>") { 249 public void parsed (String arg0) throws OptionException { 250 try { 251 repeatValue = Integer.parseInt (arg0); 252 } catch (NumberFormatException e) { 253 throw new OptionException ("Argument: " + arg0 254 + " was not a number"); 255 } 256 } 257 }); 258 259 parser.add (new Option ("arch", 260 ("On 64-bit systems," 261 + " only use test programs with the" 262 + " specified word-size (32, 64, all)." 263 + " By default, both 32-bit and 64-bit" 264 + " test programs are used"), 265 "<arch>") { 266 public void parsed (String arg0) throws OptionException { 267 if (arg0.equals("32")) 268 Prefix.set(config32); 269 else if (arg0.equals("64")) { 270 if (Host.wordSize() != 64) 271 throw new OptionException("-arch requires 64-bit"); 272 Prefix.set(config64); 273 } else if (arg0.equals("all")) 274 Prefix.set(configAll); 275 else 276 throw new OptionException( "Invalid arch value: " 277 + arg0); 278 } 279 }); 280 281 parser.add (new Option ("list-classes-only", 'n', 282 "Do not run any tests, instead list the" 283 + " classes that would have been tested") { 284 public void parsed (String nullArgument) 285 throws OptionException 286 { 287 listClassesOnly = true; 288 } 289 }); 290 291 parser.add (new Option ("stress", 292 "Run only stress tests " 293 + "(by default stress tests are excluded).") { 294 public void parsed (String nullArgument) 295 throws OptionException 296 { 297 testFilter = "^(|.*)Stress.*$"; 298 } 299 }); 300 301 parser.add (new Option ("all", 302 "Run all tests " 303 + "(by default stress tests are excluded).") { 304 public void parsed (String nullArgument) 305 throws OptionException 306 { 307 testFilter = "^.*$"; 308 } 309 }); 310 311 // Specify tests to omit. 312 parser.add (new Option ("exclude", 'e', 313 "Specify a test to exclude. Each passed" 314 + " option will be interpreted as the" 315 + " regex specification of a test to omit." 316 + " This option may be used multiple" 317 + " times.", 318 "<test-spec>") { 319 public void parsed (String arg0) { 320 excludeTests.add (arg0); 321 } 322 }); 323 324 // Specify tests to include, overriding omit. 325 parser.add (new Option ("include", 'i', 326 "Specify a test to include, ovirriding an" 327 + " omit specification. Each passed" 328 + " option will be interpreted as the" 329 + " regex specification of a test to include." 330 + " This option may be used multiple" 331 + " times.", 332 "<test-spec>") { 333 public void parsed (String arg0) { 334 includeTests.add (arg0); 335 } 336 }); 337 338 parser.add(new Option ("timeout", 339 "Specify a timeout (in seconds) to use for " + 340 "assertRunUntilStop", "<timeout>") { 341 public void parsed (String arg0) { 342 int timeout = Integer.parseInt(arg0); 343 TestCase.setTimeoutSeconds (timeout); 344 Expect.setDefaultTimeoutSeconds (timeout); 345 } 346 }); 347 348 parser.setHeader ("Usage:" 349 + " [ --console <LOG=LEVEL> ]" 350 + " [ --log <LOG=LEVEL> ]" 351 + " [ -r <repeat-count> ]" 352 + " [ --arch <arch>]" 353 + " [ -n ]" 354 + " [ --stress ]" 355 + " [ --all ]" 356 + " [-o spec...]" 357 + " [-i spec...]" 358 + " [--timeout <timeout>]" 359 + " [--unbreak ]" 360 + " [ class ... ]"); 361 return parser; 362 } 363 364 private static String programBasename; 365 /** 366 * Possible configurations. 367 */ 368 private final Prefix configAll; 369 private final Prefix config32; 370 private final Prefix config64; 371 372 /** 373 * Return the TestRunner's true basename - it could be "funit" or 374 * it could be "TestRunner". 375 * 376 * XXX: Hack, shouldn't be using static storage for this. Should 377 * this go in frysk.Prefix? 378 */ 379 public static String getProgramBasename () 380 { 381 return programBasename; 382 } 383 384 /** 385 * Create a JUnit TestRunner, using command-line arguments args, 386 * and the supplied testClasses. 387 */ 388 public Runner(String programBasename, String[] args, 389 Prefix configAll, Prefix config32, Prefix config64) { 390 // Override the print methods. 391 super(new Results(System.out)); 392 393 Prefix.set(configAll); // default 394 this.configAll = configAll; 395 this.config32 = config32; 396 this.config64 = config64; 397 398 // Tell expect the default timeout. 399 Expect.setDefaultTimeoutSeconds (TestCase.getTimeoutSeconds ()); 400 401 // Create the command line parser, and use it to parse all 402 // command line options. 403 Runner.programBasename = programBasename; 404 Parser parser = createCommandLineParser(programBasename); 405 406 otherArgs = new LinkedList(); 407 408 parser.parse(args, new FileArgumentCallback() { 409 public void notifyFile(String arg) throws OptionException { 410 otherArgs.add(arg); 411 } 412 }); 413 414 } 415 416 /** 417 * Merge two TestRunner results returning the most fatal. 418 */ 419 private int worstResult (int lhs, int rhs) { 420 if (lhs == SUCCESS_EXIT) { 421 return rhs; 422 } 423 if (lhs == FAILURE_EXIT) { 424 if (rhs == SUCCESS_EXIT) { 425 return FAILURE_EXIT; 426 } else { 427 return rhs; 428 } 429 } 430 return EXCEPTION_EXIT; 431 } 432 433 /** 434 * Run the testcases carried by testClasses. 435 * 436 * @param testClasses 437 * @return int the value of exit. 438 */ 439 public int runTestCases (Collection tests) { 440 int result = SUCCESS_EXIT; 441 result = worstResult(runCases(tests), result); 442 return result; 443 } 444 445 /** 446 * Should the known-to-be-broken tests run? 447 */ 448 private static boolean skipUnresolvedTests = true; 449 450 /** 451 * A method that returns true, and reports "UNRESOLVED". Used by 452 * test cases that want to be skipped (vis: if (broken()) return) 453 * while trying to avoid the compiler's optimizer realizing that 454 * the rest of the function is dead. 455 */ 456 static boolean unresolved(int bug, boolean unresolved) { 457 String msg = "http://sourceware.org/bugzilla/show_bug.cgi?id=" + bug; 458 return unresolved(msg, unresolved); 459 } 460 461 /** 462 * A method that returns true, and reports "UNRESOLVED". Used by 463 * test cases that want to be skipped (vis: if (broken()) return) 464 * while trying to avoid the compiler's optimizer realizing that 465 * the rest of the function is dead. 466 */ 467 static boolean unresolved(String bug, boolean unresolved) { 468 if (skipUnresolvedTests) { 469 if (unresolved) { 470 Results.addUnresolved(bug); 471 } 472 } else { 473 Results.addResolved(bug); 474 } 475 return skipUnresolvedTests && unresolved; 476 } 477 478 /** 479 * An unsupported feature; can't test. 480 */ 481 static boolean unsupported(String reason, boolean unsupported) 482 { 483 if (unsupported) { 484 Results.addUnsupported(reason); 485 } 486 // XXX: For moment do not enable unsupported tests when -u was 487 // specified as it seems to cause cascading problems. 488 return unsupported; 489 } 490 }