/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * JavaWorld Library, Copyright 2011 Bryan Chadwick * * * * FILE: ./testing/Examples.java * * * * This file is part of JavaWorld. * * * * JavaWorld is free software: you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation, either version * * 3 of the License, or (at your option) any later version. * * * * JavaWorld is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with JavaWorld. If not, see <http://www.gnu.org/licenses/>. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package testing; import java.lang.reflect.*; import util.Util; /** The parent of Example/Test classes. * <h3>Use of the Examples Class</h3> * <p> * In order to use the {@link testing.Examples Examples} * class, you simply <tt><b>import</b></tt> it, and create your own examples * class that <tt><b>extends</b></tt> {@link testing.Examples Examples}, and insert the * following <i>stub</i> method at the top: * <pre> * <b>public static void</b> main(String[] args){ test(); } * </pre> * * When you run your examples class in Eclipse, this method will start the * create an instance of your examples class, print * it, and run any test methods (methods who's names begin with <tt>test</tt>). The * resulting report can be used to verify that your tests pass, or see where any * <tt>checkExpect</tt>s failed.</tt> * </p> * * <p> * In order to test specific classes and methods, you can use the appropriate * <tt>checkExpect</tt> methods. Specially named test method <i>i.e.</i>, * <tt>testFoo</tt> <tt><b>return</b></tt> a number of <tt>checkExpect</tt>s * combined with <tt>&&</tt> (<i>and</i>ed together). * </p> * * <p> * Below is a sample, which demonstrates most of the testing features: * <style type='text/css'><!-- * .def{ color: #000000; } * .grade{ * border: solid #FF0000 1px; * background-color: #FFFF60; * } * .gcom{ font-weight: bold; background-color: #FFC0C0; } * .com{ font-style: italic; color: #880000; } * .keyw{ font-weight: bold; color: #000088; } * .num{ color: #00AA00; } * .func{ color: #BB7733; } * .str{ color: #CC00AB; } * .prim{ color: #0000FF; } * --></style> * <pre> * <span class="keyw">import</span><span class="def"> testing.Examples; * * </span><span class="keyw">public</span><span class="def"> </span><span class="keyw">class</span><span class="def"> FooExamples </span><span class="keyw">extends</span><span class="def"> Examples{ * </span><span class="keyw">public</span><span class="def"> </span><span class="keyw">static</span><span class="def"> </span><span class="keyw">void</span><span class="def"> </span><span class="func">main</span><span class="def">(</span><span class="prim">String</span><span class="def">[] args){ </span><span class="func">test</span><span class="def">(); } * * Foo f1 = </span><span class="keyw">new</span><span class="def"> </span><span class="func">Foo</span><span class="def">(</span><span class="str">"hello"</span><span class="def">); * Foo f2 = </span><span class="keyw">new</span><span class="def"> </span><span class="func">Foo</span><span class="def">(</span><span class="str">"Jimmy"</span><span class="def">); * Bar b1 = </span><span class="keyw">new</span><span class="def"> </span><span class="func">Bar</span><span class="def">(f1); * Bar b2 = </span><span class="keyw">new</span><span class="def"> </span><span class="func">Bar</span><span class="def">(f2); * * </span><span class="keyw">boolean</span><span class="def"> </span><span class="func">testFoo</span><span class="def">(){ * </span><span class="keyw">return</span><span class="def"> (</span><span class="func">checkExpect</span><span class="def">(f1.s, </span><span class="str">"hello"</span><span class="def">) && * </span><span class="func">checkExpect</span><span class="def">(f2, </span><span class="keyw">new</span><span class="def"> </span><span class="func">Foo</span><span class="def">(</span><span class="str">"Jimmy"</span><span class="def">)) && * </span><span class="func">checkExpect</span><span class="def">(f2.i, </span><span class="num">5</span><span class="def">)); * } * </span><span class="keyw">boolean</span><span class="def"> </span><span class="func">testBar</span><span class="def">(){ * </span><span class="keyw">return</span><span class="def"> (</span><span class="func">checkExpect</span><span class="def">(b1.</span><span class="func">getFoo</span><span class="def">().s, </span><span class="str">"hello"</span><span class="def">) && * </span><span class="func">checkExpect</span><span class="def">(b2.</span><span class="func">getFoo</span><span class="def">(), </span><span class="keyw">new</span><span class="def"> </span><span class="func">Foo</span><span class="def">(</span><span class="str">"Jimmy"</span><span class="def">)) && * </span><span class="func">checkExpect</span><span class="def">(b2.</span><span class="func">getFoo</span><span class="def">().i, </span><span class="num">5</span><span class="def">)); * } * } * * </span><span class="keyw">class</span><span class="def"> Foo{ * </span><span class="keyw">int</span><span class="def"> i = </span><span class="num">5</span><span class="def">; * </span><span class="prim">String</span><span class="def"> s; * </span><span class="func">Foo</span><span class="def">(</span><span class="prim">String</span><span class="def"> s){ * </span><span class="keyw">this</span><span class="def">.s = s; * } * } * * </span><span class="keyw">class</span><span class="def"> Bar{ * Foo f; * </span><span class="func">Bar</span><span class="def">(Foo f){ * </span><span class="keyw">this</span><span class="def">.f = f; * } * Foo </span><span class="func">getFoo</span><span class="def">(){ </span><span class="keyw">return</span><span class="def"> f; } * }</span> * </pre> * </p> * * */ public class Examples{ /** Run all the tests for the invoking class, <i>i.e.</i>, the outer * Examples class, passin whether or not to show all the examples */ public static void test(boolean show){ main(2,show); } /** Run all the tests for the invoking class, <i>i.e.</i>, the outer * Examples class, show all examples */ public static void test(){ main(2,true); } /** Run all the tests for the invoking class, <i>i.e.</i>, the outer * Examples class, looking up the class at <tt>depth/tt> in the call * stack. */ private static void main(int depth, boolean show){ StackTraceElement[] es = new RuntimeException().getStackTrace(); if(es.length < depth) throw new RuntimeException("Incorrect Example Class Layout"); Examples e; try{ Class<?> c = Class.forName(es[depth].getClassName()); Constructor<?> con = c.getConstructor(new Class[0]); if(!con.isAccessible())con.setAccessible(true); e = (Examples)con.newInstance(new Object[0]); }catch(Exception ex){ ex.printStackTrace(); throw new RuntimeException("Error Initializing Examples Class: "+es[1].getClassName()+ "\n Make sure YourExamples class is 'public'!"); } e.runTests(show); } /** Run all the tests and show all the instances for this * Examples class */ public boolean runTests(){ return runTests(this); } /** Run all the tests for this Examples class, tell whether * or not the Example instances should be shown */ public boolean runTests(boolean show){ return runTests(this,show); } /** Version number... */ private static double version = 0.1; /** Run all the tests and show all the instances for the * given examples class */ private boolean runTests(Examples ex){ return runTests(ex, true); } /** Run all the tests for the given examples class, tell whether * or not the Example instances should be shown */ private boolean runTests(Examples ex, boolean show){ if(!logEmpty())throw new RuntimeException("Already Tested"); rawlog(" /"+stars("Tester Version "+version, '-', MAX_WIDTH)+"\\"); String cName = ex.getClass().getSimpleName(); if(show){ rawlog(" |"+stars("Class: "+cName, '*', MAX_WIDTH)+"|"); log(" |"); try{ log(" | "+Util.display(ex)); }catch(Exception e){ log(" | !! Unable to display "+cName+ ", likely due to uninitialized Examples"); } log(" |"); } rawlog(" |"+stars("Testing", '*', MAX_WIDTH)+"|"); log(" |"); for(Method m:ex.getClass().getDeclaredMethods()){ if(m.getName().startsWith(TEST) && !Modifier.isStatic(m.getModifiers())){ if(checkSignature(m)){ try{ if(!m.isAccessible())m.setAccessible(true); boolean res = (Boolean)m.invoke(ex, new Object[0]); if(!res){ log(" |"); } }catch(Exception e){ log(" | Recieved unexpected Exception ("+e.getClass().getSimpleName()+") "+ "running method "+cName+"."+m.getName()); fail++; log(" |"); } }else{ log(" | Method "+cName+"."+m.getName()+" looks like a Test but has the "+ "wrong argument/return types"); fail++; log(" |"); } } } boolean fin = (numtests > 0 && fail == 0); if(fin){ rawlog(" \\"+stars(((numtests == 0)?"No available Tests": (numtests == 1)?"The Test Passed": (numtests == 2)?"Both Tests Passed":("All "+numtests+" Tests Passed")), '-', MAX_WIDTH)+"/"); } else{ rawlog(" \\"+stars("!! "+ ((numtests == 0)?"No Test Methods Found":(fail+" out of "+numtests+" Tests Failed"))+" !!",'-',MAX_WIDTH)+"/"); } printLog(); return fin; } /** Check the Methods signature for testing: <code><b>boolean</b> testSomething(){ ... }</code> */ private boolean checkSignature(Method m){ return (m.getReturnType().equals(Boolean.class) || m.getReturnType().equals(boolean.class)) && m.getParameterTypes().length == 0; } /** Make a bunch of Stars for bordering */ private String stars(String head, char one, int width){ int space = width-head.length(); return Util.repeat(one, (space+1)/2)+" "+head+" "+Util.repeat(one, space/2); } /** Output from all Tests... */ private static String output = ""; /** Counts for tests and errors */ private static int numtests = 0, fail = 0; /** Print-out Width*/ private static int MAX_WIDTH = 80; /** Just add the string to the output */ private void rawlog(String s){ output += s+"\n"; } /** Log a given string/result */ private void log(String s){ int newline = s.indexOf('\n'); if(newline >= 0){ log(s.substring(0,newline)); log(" | "+s.substring(newline+1)); return; } if(s.length() <= MAX_WIDTH+4){ rawlog(s+Util.repeat(' ',MAX_WIDTH-s.length()+5)+"|"); return; } int i = MAX_WIDTH+4; while(i > 0 && s.charAt(i) != ' ')i--; if(i == 0) rawlog(s); else{ rawlog(s.substring(0, i)+Util.repeat(' ',MAX_WIDTH-i+5)+"|"); log(" | "+s.substring(i)); } } /** Log a given string/result */ private boolean logEmpty(){ return output.length() == 0; } /***/ private void printLog(){ System.err.println(output); } /** Maximum difference for floating point values */ private static double DIFF = 0.000001; /** Prefix for testing methods */ private static String TEST = "test"; /** The name of our check/expect methods */ private static String CHECK = "checkExpect"; /** Should we log check/expect failures? */ private static boolean LOGFAILS = true; /** Check the given boolean against the Expected one */ public boolean checkExpect(boolean a, boolean b){ numtests++; boolean ret = a == b; if(!ret && LOGFAILS)addFailure(a,b,outerCheckExpect()); return ret; } /** Check the given character against the Expected one */ public boolean checkExpect(char a, char b){ return checkExpect((long)a,b); } /** Check the given short-integer against the Expected one */ public boolean checkExpect(short a, short b){ return checkExpect((long)a,b); } /** Check the given integer against the Expected one */ public boolean checkExpect(int a, int b){ return checkExpect((long)a,b); } /** Check the given long-integer against the Expected one */ public boolean checkExpect(long a, long b){ numtests++; boolean ret = a == b; if(!ret && LOGFAILS)addFailure(a,b,outerCheckExpect()); return ret; } /** Check the given floating-point number against the Expected one */ public boolean checkExpect(float a, float b){ return checkExpect((double)a,b); } /** Check the given double-precision number against the Expected one */ public boolean checkExpect(double a, double b){ numtests++; boolean ret = Math.abs(((double)a)-b)<DIFF; if(!ret && LOGFAILS)addFailure(a,b,outerCheckExpect()); return ret; } /** Check the given String against the Expected one */ public boolean checkExpect(String a, String b){ return checkExpect(a,b,String.class); } /** Check the given Object against the Expected one */ public boolean checkExpect(Object a, Object b){ numtests++; if(a == b)return true; if(a == null || b == null){ if(LOGFAILS)addFailure(a,b,outerCheckExpect()); return false; } return checkExpect(a,b,a.getClass()); } /** Find the last checkExpect method. We assume there is at least one... */ private String outerCheckExpect(){ StackTraceElement[] es = new RuntimeException().getStackTrace(); for(int i = 1; i < es.length; i++){ if(!es[i].getMethodName().equals(CHECK)){ StackTraceElement e = es[i]; return "\""+e.getFileName()+"\": Line "+e.getLineNumber()+" in method \""+e.getMethodName()+"()\""; } } throw new RuntimeException("Illegal Testing Entry!"); } /** Compare two objects of the same (given) Class */ private boolean checkExpect(Object a, Object b, Class<?> c){ if(a == b)return true; if(a == null || b == null){ if(LOGFAILS)addFailure(a,b,outerCheckExpect()); return false; } if(!(a.getClass().equals(b.getClass()))){ if(LOGFAILS)addFailure(a,b,outerCheckExpect()); return false; } if(c.isPrimitive() || c.equals(String.class)){ boolean ret = a.equals(b); if(!ret && LOGFAILS)addFailure(a,b,outerCheckExpect()); return ret; } Field fs[] = Util.fields(a.getClass()); for(Field f:fs){ try{ if(!checkExpect(f.get(a), f.get(b), f.getType())) return false; }catch(Exception e){ if(LOGFAILS)addException(e, outerCheckExpect()); return false; } } return true; } /***/ private void addFailure(Object a, Object b, String location){ log(" | ! CheckExpect Failed in "+location+""); log(" | Received: "+Util.display(a," ")); log(" | Expected: "+Util.display(b," ")); fail++; } /***/ private void addException(Exception e, String location){ log(" | ! CheckExpect unexpected exception in "+location+""); fail++; } }