001/* Run the Tcl tests under JUnit. 002 003 Copyright (c) 2011-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028 029package ptolemy.util.test.junit; 030 031import static org.junit.Assert.assertEquals; 032 033import java.io.File; 034import java.io.FilenameFilter; 035import java.io.IOException; 036import java.lang.reflect.Method; 037import java.util.Arrays; 038import java.util.Comparator; 039import java.util.Locale; 040 041import org.junit.AfterClass; 042import org.junit.Test; 043import org.junit.runner.RunWith; 044 045import junitparams.JUnitParamsRunner; 046import junitparams.Parameters; 047 048/////////////////////////////////////////////////////////////////// 049//// TclTests 050/** 051 * Run the Tcl tests under JUnit. 052 * 053 * <p> 054 * This test must be run from the directory that contains the auto/ directory, 055 * for example: 056 * </p> 057 * 058 * <pre> 059 * (cd ~/ptII/ptolemy/actor/lib/io/test; java -classpath ${PTII}:${PTII}/lib/ptjacl.jar:${PTII}/lib/junit-4.8.2.jar:${PTII}/lib/JUnitParams-0.3.0.jar org.junit.runner.JUnitCore ptolemy.util.test.junit.TclTests) 060 * </pre> 061 * 062 * <p> 063 * This test uses JUnitParams from <a 064 * href="http://code.google.com/p/junitparams/#in_browser" 065 * >http://code.google.com/p/junitparams/</a>, which is released under <a 066 * href="http://www.apache.org/licenses/LICENSE-2.0#in_browser">Apache License 067 * 2.0</a>. 068 * </p> 069 * 070 * @author Christopher Brooks 071 * @version $Id$ 072 * @since Ptolemy II 10.0 073 * @Pt.ProposedRating Red (cxh) 074 * @Pt.AcceptedRating Red (cxh) 075 */ 076@RunWith(JUnitParamsRunner.class) 077public class TclTests { 078 079 /** 080 * Call the Tcl doneTests command to print out the number of errors. 081 * 082 * @exception Throwable 083 * If the class, constructor or method cannot be found. or if 084 * the Interp cannot be instantiated. 085 */ 086 @AfterClass 087 public static void doneTests() throws Throwable { 088 089 if (_tclFileCount == 0) { 090 // No .tcl files were found, so testDefs.tcl, which 091 // defines the Tcl doneTests command has *not* been 092 // sourced. So, we return. 093 return; 094 } 095 096 // util/testsuite/testDefs.tcl doneTests tcl command checks 097 // the value of the reallyExit tcl variable. If reallyExit is 098 // not present or 1, then ::tycho::TopLevel::exitProgram is 099 // called. We don't want that because it prints an error 100 // message, so we set reallyExit to 0. 101 _setVarMethod.invoke(_interp, new Object[] { "reallyExit", 102 _tclObjectZero, 1 /*TCL.GLOBAL_ONLY*/ }); 103 104 // Invoke the doneTests Tcl command which prints the number of 105 // tests. 106 try { 107 _evalMethod.invoke(_interp, new Object[] { "doneTests", 0 }); 108 } catch (Throwable throwable) { 109 if (!_tclExceptionClass.isInstance(throwable.getCause())) { 110 throw throwable; 111 } else { 112 Integer completionCode = (Integer) _getCompletionCodeMethod 113 .invoke(throwable.getCause(), new Object[] {}); 114 if (completionCode.intValue() == 1 /** TCL.ERROR */ 115 ) { 116 // The completion code was 1, which means that the 117 // command could not be completed successfully. 118 119 // The Tcl errorInfo global variable will have information 120 // about what went wrong. 121 Object errorInfoTclObject = _getVarMethod.invoke(_interp, 122 new Object[] { "errorInfo", null, 123 1 /*TCL.GLOBAL_ONLY*/ 124 }); 125 throw new Exception( 126 "Evaluating the Tcl method \"doneTests\" " 127 + "resulted in a TclException being thrown.\nThe Tcl " 128 + "errorInfo global variable has the value:\n" 129 + errorInfoTclObject); 130 } 131 } 132 } 133 } 134 135 /** 136 * Return a two dimensional array of arrays of strings that name the .tcl files 137 * to be executed. If there are no .tcl files, return a list with one element that 138 * has the value of the {@link #THERE_ARE_NO_TCL_TESTS} field. 139 * 140 * @return The List of tcl tests. 141 * @exception IOException If there is a problem accessing the auto/ directory. 142 */ 143 public Object[] parametersForRunTclFile() throws IOException { 144 String[] tclFiles = new File(".").list(new FilenameFilter() { 145 /** 146 * Return true if the file name ends with .tcl and is not 147 * alljtests.tcl or testDefs.tcl 148 * 149 * @param directory 150 * Ignored 151 * @param name 152 * The name of the file. 153 * @return true if the file name ends with .xml or .moml 154 */ 155 @Override 156 public boolean accept(File directory, String name) { 157 String fileName = name.toLowerCase(Locale.getDefault()); 158 if (fileName.endsWith(".tcl")) { 159 // alljsimpletests.tcl calls exit, 160 // which results in JUnit 161 // producing 162 // "junit.framework.AssertionFailedError: 163 // Forked Java VM exited 164 // abnormally. Please note the 165 // time in the report does not 166 // reflect the time until the VM 167 // exit." 168 169 if (!fileName.endsWith("alljsimpletests.tcl") 170 && !fileName.endsWith("alljtests.tcl") 171 && !fileName.endsWith("testdefs.tcl")) { 172 return true; 173 } 174 } 175 return false; 176 } 177 }); 178 179 if (tclFiles.length > 0) { 180 int i = 0; 181 Object[][] data = new Object[tclFiles.length][1]; 182 for (String tclFile : tclFiles) { 183 data[i++][0] = new File(tclFile).getCanonicalPath(); 184 } 185 // Sort the tcl files so that _Configuration.tcl is first 186 // in ptolemy/actor/gui/test 187 // File.list() returns files in a different order 188 // on different platforms. So much for write once, run everywhere. 189 Arrays.sort(data, new Comparator<Object[]>() { 190 @Override 191 public int compare(final Object[] entry1, 192 final Object[] entry2) { 193 final String file1 = (String) entry1[0]; 194 final String file2 = (String) entry2[0]; 195 return file1.compareTo(file2); 196 } 197 }); 198 return data; 199 } else { 200 return new Object[][] { { THERE_ARE_NO_TCL_TESTS } }; 201 } 202 } 203 204 /** 205 * Run a tclFile. 206 207 * <p>Timeout after 480000 ms. The 208 * ptolemy/cg/kernel/generic/program/procedural/java/test/AutoAdapter.tcl 209 * test requires more than 240 seconds.</p> 210 * 211 * @param tclFile 212 * The full path to the .tcl file to be executed. If tclFile 213 * ends with the value of the {@link #THERE_ARE_NO_TCL_TESTS}, 214 * then the method returns immediately. 215 * @exception Throwable If thrown while executing the tclFile. 216 */ 217 @Test 218 @Parameters 219 public void RunTclFile(String tclFile) throws Throwable { 220 if (tclFile.endsWith(THERE_ARE_NO_TCL_TESTS)) { 221 System.out.println( 222 "No tcl tests in " + System.getProperty("user.dir")); 223 System.out.flush(); 224 return; 225 } 226 // Keep track of the number of Tcl files evaluated 227 // If 1 or more files were evaluated, then we call doneTests. 228 TclTests._incrementTclFileCount(); 229 230 System.out.println(tclFile); 231 System.out.flush(); 232 try { 233 _evalFileMethod.invoke(_interp, new Object[] { tclFile }); 234 } catch (Throwable throwable) { 235 if (!_tclExceptionClass.isInstance(throwable.getCause())) { 236 throw throwable; 237 } else { 238 Integer completionCode = (Integer) _getCompletionCodeMethod 239 .invoke(throwable.getCause(), new Object[] {}); 240 if (completionCode.intValue() == 1 /** TCL.ERROR */ 241 ) { 242 // The completion code was 1, which means that the 243 // command could not be completed successfully. 244 245 // The Tcl errorInfo global variable will have information 246 // about what went wrong. 247 Object errorInfoTclObject = null; 248 try { 249 errorInfoTclObject = _getVarMethod.invoke(_interp, 250 new Object[] { "errorInfo", null, 251 1 /*TCL.GLOBAL_ONLY*/ 252 }); 253 throw new Exception("Evaluating the Tcl file \"" 254 + tclFile 255 + "\"resulted in a TclException being thrown.\nThe Tcl " 256 + "errorInfo global variable has the value:\n" 257 + errorInfoTclObject); 258 } catch (Throwable throwable2) { 259 throwable2.printStackTrace(); 260 throw new Exception("Evaluating the Tcl file \"" 261 + tclFile 262 + "\"resulted in a TclException being thrown.\n" 263 + "The Tcl errorInfo variable could not be obtained.\n" 264 + throwable2, throwable); 265 } 266 } 267 } 268 } 269 270 // Get the value of the Tcl FAILED global variable. 271 // We check for non-zero results for *each* .tcl file. 272 Object newFailedCountTclObject = _getVarMethod.invoke(_interp, 273 new Object[] { "FAILED", null, 1 /*TCL.GLOBAL_ONLY*/ 274 }); 275 int newFailed = Integer.parseInt(newFailedCountTclObject.toString()); 276 int lastFailed = Integer.parseInt(_failedTestCount.toString()); 277 278 // We only report if the number of test failures has increased. 279 // this prevents us from reporting cascading errors if the 280 // first .tcl file has a failure. 281 TclTests._setFailedTestCount(_newInstanceTclIntegerMethod.invoke(null, 282 new Object[] { Integer.valueOf(newFailed) })); 283 284 // If the Tcl FAILED global variable is not equal to the 285 // previous number of failures, then add a failure. 286 assertEquals("Number of failed tests is has increased.", lastFailed, 287 newFailed); 288 } 289 290 /////////////////////////////////////////////////////////////////// 291 //// protected methods //// 292 293 /** Increment the count of the number of tcl files visited. 294 * Keep track of the number of Tcl files evaluated 295 * If 1 or more files were evaluated, then we call doneTests. 296 */ 297 protected static void _incrementTclFileCount() { 298 // To avoid FindBugs: Write to static field from instance method 299 _tclFileCount++; 300 } 301 302 /** Set the cached value of the count of the number of failed tests. 303 * @param count The object representing the number of failed tests. 304 */ 305 protected static void _setFailedTestCount(Object count) { 306 // To avoid FindBugs: Write to static field from instance method 307 _failedTestCount = count; 308 } 309 310 /////////////////////////////////////////////////////////////////// 311 //// protected variables //// 312 313 /** 314 * A special string that is passed when there are no tcl tests. This is 315 * necessary to avoid an exception in the JUnitParameters. 316 */ 317 protected final static String THERE_ARE_NO_TCL_TESTS = "ThereAreNoTclTests"; 318 319 /////////////////////////////////////////////////////////////////// 320 //// private variables //// 321 322 /** The tcl.lang.Interp.eval(String, int) method. */ 323 private static Method _evalMethod; 324 325 /** The tcl.lang.Interp.evalFile(String) method. */ 326 private static Method _evalFileMethod; 327 328 /** The number of failed tests. Each .tcl file tests to see if 329 * the number has increased. 330 */ 331 private static Object _failedTestCount; 332 333 /** The tcl.lang.TclException.getCompletionCode() method. */ 334 private static Method _getCompletionCodeMethod; 335 336 /** The tcl.lang.Interp.getVar(String name1, String name2, int flags) method. */ 337 private static Method _getVarMethod; 338 339 /** 340 * The tcl.lang.Interp class. We use reflection here to avoid false 341 * dependencies if auto/ does not exist. 342 */ 343 private static Class<?> _interpClass; 344 345 /** 346 * The tcl.lang.Interp object upon which we invoke evalFile(String). 347 */ 348 private static Object _interp; 349 350 /** The newInstance() method of the tcl.lang.TclInteger class. */ 351 private static Method _newInstanceTclIntegerMethod; 352 353 /** The tcl.lang.Interp.setVar(String name1, String name2, int flags) method. */ 354 private static Method _setVarMethod; 355 356 /** The tcl.lang.TclException class. **/ 357 private static Class<?> _tclExceptionClass; 358 359 /** Keep track of the number of Tcl files evaluated 360 * If 1 or more files were evaluated, then we call doneTests. 361 */ 362 private static int _tclFileCount = 0; 363 364 /** 365 * The tcl.lang.TclObject class. We use reflection here to avoid false 366 * dependencies if auto/ does not exist. 367 */ 368 private static Class<?> _tclObjectClass; 369 370 /** 371 * A tcl.lang.TclObject that has the integer value 0. 372 * Used when we call the doneTests Tcl method. 373 */ 374 private static Object _tclObjectZero; 375 376 // We place initialization of the _interp in a static block so 377 // that it happens once per directory of tcl files. The doneTests() method 378 // prints the number of test case failures for us. 379 static { 380 try { 381 // ptolemy.actor.lib.test.NonStrictTest checks isRunningNightlyBuild and 382 // throws an exception if trainingMode is true. 383 System.setProperty("ptolemy.ptII.isRunningNightlyBuild", "true"); 384 385 // ptolemy.util.StringUtilities.exit() checks ptolemy.ptII.doNotExit. 386 System.setProperty("ptolemy.ptII.doNotExit", "true"); 387 388 _interpClass = Class.forName("tcl.lang.Interp"); 389 _interp = _interpClass.newInstance(); 390 391 _evalMethod = _interpClass.getMethod("eval", 392 new Class[] { String.class, Integer.TYPE }); 393 394 _evalFileMethod = _interpClass.getMethod("evalFile", String.class); 395 396 _getVarMethod = _interpClass.getMethod("getVar", 397 new Class[] { String.class, String.class, Integer.TYPE }); 398 399 _tclObjectClass = Class.forName("tcl.lang.TclObject"); 400 _setVarMethod = _interpClass.getMethod("setVar", new Class[] { 401 String.class, _tclObjectClass, Integer.TYPE }); 402 403 // Create a TclObject with value 0 for use with the doneTests Tcl proc. 404 Class<?> tclIntegerClass = Class.forName("tcl.lang.TclInteger"); 405 _newInstanceTclIntegerMethod = tclIntegerClass 406 .getMethod("newInstance", new Class[] { Integer.TYPE }); 407 408 _tclExceptionClass = Class.forName("tcl.lang.TclException"); 409 410 _getCompletionCodeMethod = _tclExceptionClass 411 .getMethod("getCompletionCode", new Class[] {}); 412 413 _tclObjectZero = _newInstanceTclIntegerMethod.invoke(null, 414 new Object[] { Integer.valueOf(0) }); 415 416 _failedTestCount = _newInstanceTclIntegerMethod.invoke(null, 417 new Object[] { Integer.valueOf(0) }); 418 419 } catch (Throwable throwable) { 420 // Exceptions sometimes get marked as multiple failures here so 421 // we print the stack to aid debugging. 422 throwable.printStackTrace(); 423 throw new RuntimeException(throwable); 424 } 425 } 426 427}