JTap.java
Go to the documentation of this file.
00001 /** 00002 * @file JTap.java 00003 * @brief Unit testing framework for Java based on the Test Anything Protocol. 00004 * 00005 * @author Patrick LeBoutillier <patl at cpan.org> 00006 * 00007 * @note This file is a copy of the JTap.java file which is part of the 00008 * JTap project (http://svn.solucorp.qc.ca/repos/solucorp/JTap/trunk/). 00009 * The original implementation has been modified by Andreas Schuh as 00010 * part of the BASIS project at SBIA. 00011 * 00012 * Copyright (c) Patrick LeBoutillier.<br /> 00013 * Copyright (c) 2011, University of Pennsylvania.<br /> 00014 * All rights reserved. 00015 * 00016 * This program is free software; you can redistribute it and/or modify 00017 * it under the terms of the GNU General Public License as published by 00018 * the Free Software Foundation; either version 2 of the License, or 00019 * any later version. 00020 * 00021 * This program is distributed in the hope that it will be useful, 00022 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00024 * GNU General Public License for more details. 00025 * 00026 * @sa http://testanything.org/wiki/index.php/Tap-functions 00027 * 00028 * Contact: SBIA Group <sbia-software at uphs.upenn.edu> 00029 * 00030 * @ingroup BasisJavaUtilities 00031 */ 00032 00033 import java.io.* ; 00034 00035 00036 public class JTap { 00037 final private String version = "1.0" ; 00038 00039 private boolean plan_set = false ; 00040 private boolean no_plan = false ; 00041 private boolean skip_all = false ; 00042 private boolean test_died = false ; 00043 private int expected_tests = 0 ; 00044 private int executed_tests = 0 ; 00045 private int failed_tests = 0 ; 00046 private boolean exit = true ; 00047 private String todo = null ; 00048 00049 private PrintStream out = null ; 00050 private PrintStream err = null ; 00051 00052 00053 public JTap(){ 00054 this(true) ; 00055 } 00056 00057 00058 public JTap(boolean really_exit){ 00059 out = System.out ; 00060 err = System.err ; 00061 exit = really_exit ; 00062 } 00063 00064 00065 00066 00067 synchronized public int plan_no_plan(){ 00068 if (plan_set){ 00069 die("You tried to plan twice!") ; 00070 } 00071 00072 plan_set = true ; 00073 no_plan = true ; 00074 00075 return 1 ; 00076 } 00077 00078 00079 synchronized public int plan_skip_all(String reason){ 00080 if (plan_set){ 00081 die("You tried to plan twice!") ; 00082 } 00083 00084 print_plan(0, "Skip " + reason) ; 00085 00086 skip_all = true ; 00087 plan_set = true ; 00088 00089 exit(0) ; 00090 00091 return 0 ; 00092 } 00093 00094 00095 synchronized public int plan_tests(int tests){ 00096 if (plan_set){ 00097 die("You tried to plan twice!") ; 00098 } 00099 00100 if (tests == 0){ 00101 die("You said to run 0 tests! You've got to run something.") ; 00102 } 00103 00104 print_plan(tests) ; 00105 expected_tests = tests ; 00106 00107 plan_set = true ; 00108 00109 return tests ; 00110 } 00111 00112 00113 private void print_plan(int expected){ 00114 print_plan(expected, null) ; 00115 } 00116 00117 00118 synchronized private void print_plan(int expected_tests, String directive){ 00119 out.print("1.." + expected_tests) ; 00120 if (directive != null){ 00121 out.print(" # " + directive) ; 00122 } 00123 out.print("\n") ; 00124 out.flush() ; 00125 } 00126 00127 00128 00129 00130 public boolean pass(String name){ 00131 return ok(true, name) ; 00132 } 00133 00134 00135 public boolean fail(String name){ 00136 return ok(false, name) ; 00137 } 00138 00139 00140 public boolean ok(boolean result){ 00141 return ok(result, null) ; 00142 } 00143 00144 00145 /* 00146 This is the workhorse method that actually 00147 prints the tests result. 00148 */ 00149 synchronized public boolean ok(boolean result, String name){ 00150 if (! plan_set){ 00151 die("You tried to run a test without a plan! Gotta have a plan.") ; 00152 } 00153 00154 executed_tests++ ; 00155 00156 if (name != null) { 00157 if (name.matches("[\\d\\s]+")){ 00158 diag(" You named your test '" + name 00159 + "'. You shouldn't use numbers for your test names.") ; 00160 diag(" Very confusing.") ; 00161 } 00162 } 00163 00164 if (! result){ 00165 out.print("not ") ; 00166 failed_tests++ ; 00167 } 00168 out.print("ok " + executed_tests) ; 00169 00170 if (name != null) { 00171 out.print(" - ") ; 00172 out.print(name.replaceAll("#", "\\\\#")) ; 00173 } 00174 00175 if (todo != null){ 00176 out.print(" # TODO " + todo) ; 00177 if (! result){ 00178 failed_tests-- ; 00179 } 00180 } 00181 00182 out.print("\n") ; 00183 out.flush() ; 00184 if (! result){ 00185 Throwable t = new Throwable() ; 00186 StackTraceElement stack[] = t.getStackTrace() ; 00187 String file = null ; 00188 String clas = null ; 00189 String func = null ; 00190 int line = 0 ; 00191 00192 try { 00193 for (int i = 0 ; i < stack.length ; i++){ 00194 Class c = Class.forName(stack[i].getClassName()) ; 00195 if (! JTap.class.isAssignableFrom(c)){ 00196 // We are outside a JTap object, so this is probably the callpoint 00197 file = stack[i].getFileName() ; 00198 clas = c.getName() ; 00199 func = stack[i].getMethodName() ; 00200 line = stack[i].getLineNumber() ; 00201 break ; 00202 } 00203 } 00204 } 00205 catch (Exception e){ 00206 e.printStackTrace() ; 00207 } 00208 00209 if (name != null){ 00210 diag(" Failed " + (todo == null ? "" : "(TODO) ") + "test '" + name + "'") ; 00211 diag(" in " + file + ":" + func + "() at line " + line + ".") ; 00212 } 00213 else { 00214 diag(" Failed " + (todo == null ? "" : "(TODO) ") + "test in " + file + ":" + func + "() at line " + line + ".") ; 00215 } 00216 } 00217 00218 return result ; 00219 } 00220 00221 00222 private boolean equals(Object result, Object expected){ 00223 boolean r ; 00224 00225 if ((result == null)&&(expected == null)){ 00226 r = true ; 00227 } 00228 else if ((result == null)||(expected == null)){ 00229 r = false ; 00230 } 00231 else { 00232 r = result.equals(expected) ; 00233 } 00234 00235 return r ; 00236 } 00237 00238 00239 private boolean matches(Object result, String pattern){ 00240 boolean r ; 00241 00242 if ((result == null)||(pattern == null)){ 00243 r = false ; 00244 } 00245 else { 00246 r = result.toString().matches(pattern) ; 00247 } 00248 00249 return r ; 00250 } 00251 00252 00253 private void is_diag(Object result, Object expected){ 00254 diag(" got: '" + result + "'") ; 00255 diag(" expected: '" + expected + "'") ; 00256 } 00257 00258 00259 public boolean is(Object result, Object expected){ 00260 return is(result, expected, null) ; 00261 } 00262 00263 00264 public boolean is(Object result, Object expected, String name){ 00265 boolean r = ok(equals(result, expected), name) ; 00266 if (! r){ 00267 is_diag(result, expected) ; 00268 } 00269 return r ; 00270 } 00271 00272 00273 public boolean is(long result, long expected){ 00274 return is(new Long(result), new Long(expected)) ; 00275 } 00276 00277 00278 public boolean is(long result, long expected, String name){ 00279 return is(new Long(result), new Long(expected), name) ; 00280 } 00281 00282 00283 public boolean is(double result, double expected){ 00284 return is(new Double(result), new Double(expected)) ; 00285 } 00286 00287 00288 public boolean is(double result, double expected, String name){ 00289 return is(new Double(result), new Double(expected), name) ; 00290 } 00291 00292 00293 public boolean isnt(Object result, Object expected){ 00294 return isnt(result, expected, null) ; 00295 } 00296 00297 00298 public boolean isnt(Object result, Object expected, String name){ 00299 boolean r = ok(! equals(result, expected), name) ; 00300 if (! r){ 00301 is_diag(result, expected) ; 00302 } 00303 return r ; 00304 } 00305 00306 00307 public boolean isnt(long result, long expected){ 00308 return isnt(new Long(result), new Long(expected)) ; 00309 } 00310 00311 00312 public boolean isnt(long result, long expected, String name){ 00313 return isnt(new Long(result), new Long(expected), name) ; 00314 } 00315 00316 00317 public boolean isnt(double result, double expected){ 00318 return isnt(new Double(result), new Double(expected)) ; 00319 } 00320 00321 00322 public boolean isnt(double result, double expected, String name){ 00323 return isnt(new Double(result), new Double(expected), name) ; 00324 } 00325 00326 00327 public boolean like(Object result, String pattern){ 00328 return like(result, pattern, null) ; 00329 } 00330 00331 00332 public boolean like(Object result, String pattern, String name){ 00333 boolean r = ok(matches(result, pattern), name) ; 00334 if (! r){ 00335 diag(" " + result + " doesn't match '" + pattern + "'") ; 00336 } 00337 return r ; 00338 } 00339 00340 00341 public boolean unlike(Object result, String pattern){ 00342 return unlike(result, pattern, null) ; 00343 } 00344 00345 00346 public boolean unlike(Object result, String pattern, String name){ 00347 boolean r = ok(! matches(result, pattern), name) ; 00348 if (! r){ 00349 diag(" " + result + " matches '" + pattern + "'") ; 00350 } 00351 return r ; 00352 } 00353 00354 00355 public boolean isa_ok(Object o, Class c){ 00356 return isa_ok(o, c, null) ; 00357 } 00358 00359 00360 public boolean isa_ok(Object o, Class c, String name){ 00361 boolean r = false ; 00362 if ((o == null)||(c == null)){ 00363 r = false ; 00364 } 00365 else { 00366 r = ok(c.isInstance(o), name) ; 00367 } 00368 if (! r){ 00369 diag(" Object isn't a '" + c.getName() + "' it's a '" + o.getClass().getName() + "'") ; 00370 } 00371 00372 return r ; 00373 } 00374 00375 00376 00377 00378 synchronized public void skip(String reason){ 00379 skip(reason, 1) ; 00380 } 00381 00382 00383 synchronized public void skip(String reason, int n){ 00384 for (int i = 0 ; i < n ; i++){ 00385 executed_tests++ ; 00386 out.print("ok " + executed_tests + " # skip " + reason + "\n") ; 00387 out.flush() ; 00388 } 00389 throw new JTapSkipException(reason) ; 00390 } 00391 00392 00393 synchronized public void todo_start(String reason){ 00394 if (reason.equals("")){ 00395 reason = null ; 00396 } 00397 todo = reason ; 00398 } 00399 00400 00401 synchronized public void todo_end(){ 00402 todo = null ; 00403 } 00404 00405 00406 synchronized public boolean diag(String msg){ 00407 if (msg != null){ 00408 String lines[] = msg.split("\n") ; 00409 StringBuffer buf = new StringBuffer() ; 00410 for (int i = 0 ; i < lines.length ; i++){ 00411 buf.append("# " + lines[i] + "\n") ; 00412 } 00413 out.print(buf) ; 00414 out.flush() ; 00415 } 00416 return false ; 00417 } 00418 00419 00420 00421 00422 synchronized private void die(String reason){ 00423 err.println(reason) ; 00424 test_died = true ; 00425 exit(255) ; 00426 } 00427 00428 00429 synchronized public void BAIL_OUT(String reason){ 00430 out.println("Bail out! " + reason) ; 00431 out.flush() ; 00432 exit(255) ; 00433 } 00434 00435 00436 private int cleanup(){ 00437 int rc = 0 ; 00438 00439 if (! plan_set){ 00440 diag("Looks like your test died before it could output anything.") ; 00441 return rc ; 00442 } 00443 00444 if (test_died){ 00445 diag("Looks like your test died just after " + executed_tests + ".") ; 00446 return rc ; 00447 } 00448 00449 if ((! skip_all)&&(no_plan)){ 00450 print_plan(executed_tests) ; 00451 } 00452 00453 if ((! no_plan)&&(expected_tests < executed_tests)) { 00454 diag("Looks like you planned " + expected_tests + " test" + (expected_tests > 1 ? "s" : "") + " but ran " 00455 + (executed_tests - expected_tests) + " extra.") ; 00456 rc = -1 ; 00457 } 00458 00459 if ((! no_plan)&&(expected_tests > executed_tests)) { 00460 diag("Looks like you planned " + expected_tests + " test" + (expected_tests > 1 ? "s" : "") + " but only ran " 00461 + executed_tests + ".") ; 00462 } 00463 00464 if (failed_tests > 0){ 00465 diag("Looks like you failed " + failed_tests + " test" + (failed_tests > 1 ? "s" : "") + " of " + executed_tests + ".") ; 00466 } 00467 00468 return rc ; 00469 } 00470 00471 00472 synchronized public int exit_status(){ 00473 if ((no_plan)||(! plan_set)){ 00474 return failed_tests ; 00475 } 00476 00477 if (expected_tests < executed_tests){ 00478 return executed_tests - expected_tests ; 00479 } 00480 00481 return failed_tests + (expected_tests - executed_tests) ; 00482 } 00483 00484 00485 synchronized public void exit(){ 00486 exit(exit_status()) ; 00487 } 00488 00489 00490 synchronized private void exit(int rc){ 00491 int alt_rc = cleanup() ; 00492 if (alt_rc != 0){ 00493 rc = alt_rc ; 00494 } 00495 if (exit){ 00496 System.exit(rc) ; 00497 } 00498 else { 00499 throw new JTapExitException(rc) ; 00500 } 00501 } 00502 } 00503 00504 00505 00506 00507 class JTapException extends RuntimeException { 00508 JTapException(String msg){ 00509 super(msg) ; 00510 } 00511 } 00512 00513 00514 00515 00516 class JTapExitException extends JTapException { 00517 JTapExitException(int rc){ 00518 super("exit " + rc) ; 00519 } 00520 } 00521 00522 00523 00524 00525 class JTapSkipException extends JTapException { 00526 JTapSkipException(String reason){ 00527 super("skip " + reason) ; 00528 } 00529 } 00530 00531 00532