001 /* 002 * Copyright (C) 2000-2001 Iowa State University 003 * 004 * This file is part of mjc, the MultiJava Compiler, adapted for ESC/Java2. 005 * 006 * This program is free software; you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation; either version 2 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program; if not, write to the Free Software 018 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 019 * 020 * $Id: Utils.java,v 1.10 2005/01/15 03:21:43 cok Exp $ 021 * Author: David R. Cok 022 */ 023 024 package junitutils; 025 import java.io.PrintStream; 026 import java.io.*; 027 import java.lang.reflect.Method; 028 029 /** This class contains miscellaneous (static) utility functions that are 030 useful in writing JUnit functional tests. 031 */ 032 public class Utils { 033 034 /** Setting this field to true disables the capturing of the output; 035 one would do this only for debugging purposes. 036 */ 037 static public boolean disable = false; 038 039 /** A cached value of the usual System out stream. */ 040 //@ non_null spec_public 041 final private static PrintStream pso = System.out; 042 043 /** A cached value of the usual System err stream. */ 044 //@ non_null spec_public 045 final private static PrintStream pse = System.err; 046 047 /** Redirects System.out and System.err to the given PrintStream. 048 Note that setStreams/restoreStreams operate on the global 049 values of System.out and System.err; these implementations 050 are not synchronized - you will need to take care of any 051 race conditions if you utilize these in more than one thread. 052 053 @param ps The stream that is the new output and error stream 054 */ 055 //@ requires ps != null; 056 //@ ensures System.out == ps && System.err == ps; 057 static public void setStreams(PrintStream ps) { 058 if (disable) return; 059 pso.flush(); 060 pse.flush(); 061 System.setOut(ps); 062 System.setErr(ps); 063 } 064 065 /** Creates a new output stream (which is returned) and makes it 066 the stream into which the standard and error outputs are captured. 067 068 @return an output stream into which standard and error output 069 is captured 070 */ 071 //@ ensures \result != null; 072 static public ByteArrayOutputStream setStreams() { 073 ByteArrayOutputStream ba = new ByteArrayOutputStream(10000); 074 PrintStream ps = new PrintStream(ba); 075 setStreams(ps); 076 return ba; 077 } 078 // TODO - note the hard-coded size of the stream above. It needs to be 079 // variable, or at least be sure to capture overflows. The size needs to 080 // be large enough to hold the output of a test. 081 082 /** Restores System.out and System.err to the initial, 083 system-defined values. It is ok to call this method 084 even if setStreams has not been called. 085 <p> 086 Note that setStreams/restoreStreams operate on the global 087 values of System.out and System.err; these implementations 088 are not synchronized - you will need to take care of any 089 race conditions if you utilize these in more than one thread. 090 */ 091 //@ ensures System.out == pso && System.err == pse; 092 static public void restoreStreams() { 093 restoreStreams(false); 094 } 095 096 /** Restores System.out and System.err to the initial, 097 system-defined values. It is ok to call this method 098 even if setStreams has not been called. 099 <p> 100 Note that setStreams/restoreStreams operate on the global 101 values of System.out and System.err; these implementations 102 are not synchronized - you will need to take care of any 103 race conditions if you utilize these in more than one thread. 104 105 * @param close if true, the current output and error streams 106 * are closed before being reset (if they are not currently the 107 * System output and error streams) 108 */ 109 static public void restoreStreams(boolean close) { 110 if (disable) return; 111 if (close) { 112 if (pso != System.out) pso.close(); 113 if (pse != System.err) pse.close(); 114 } 115 System.setOut(pso); 116 System.setErr(pse); 117 } 118 119 120 121 /** Parses a string into arguments as if it were a command-line, using 122 the QuoteTokenizer to parse the tokens. 123 124 @param s The String to parse 125 @return The input string parsed into command-line arguments 126 */ 127 //@ requires s != null; 128 //@ ensures \result != null; 129 //@ ensures !\nonnullelements(\result); 130 static public String[] parseLine(String s) { 131 QuoteTokenizer q = new QuoteTokenizer(s); 132 java.util.ArrayList args = new java.util.ArrayList(); 133 while (q.hasMoreTokens()) { 134 String a = q.nextToken(); 135 args.add(a); 136 } 137 return (String[])args.toArray(new String[args.size()]); 138 } 139 140 /** An enumerator that parses a string into tokens, according to the 141 rules a command-line would use. White space separates tokens, 142 with double-quoted and single-quoted strings recognized. 143 */ 144 // FIXME - does not handle escape sequences in strings, nor 145 // quoted strings that are not the whole token, e.g. 146 // a b c+"asd" should be three tokens, "a", "b", and c+"asd" . 147 static public class QuoteTokenizer { 148 /** The String being tokenized */ 149 /*@ non_null spec_public */ final private String ss; 150 151 /** A char array representation of the String being tokenized */ 152 /*@ non_null spec_public */ final private char[] cc; 153 154 /** The position in the char array */ 155 /*@ spec_public */ 156 private int pos = 0; /*@ invariant pos >= 0; 157 invariant pos <= cc.length; 158 */ 159 160 /** Initializes the tokenizer with the given String 161 * @param s the String to be tokenized 162 */ 163 //@ requires s != null; 164 //@ modifies ss,cc; 165 //@ ensures s == ss; 166 public QuoteTokenizer(String s) { 167 ss = s; 168 cc = s.toCharArray(); 169 } 170 171 /*@ 172 ensures \result ==> (pos < cc.length); 173 ensures \result == !(\forall int i; pos<=i && i < cc.length; 174 Character.isWhitespace(cc[i])); 175 model public pure boolean moreTokens(); 176 */ 177 178 /** 179 * @return true if there are more tokens to be returned 180 */ 181 //@ modifies pos; 182 //@ ensures \result == \old(moreTokens()); 183 //@ ensures \result ==> !Character.isWhitespace(cc[pos]); 184 //@ ensures moreTokens() == \old(moreTokens()); 185 public boolean hasMoreTokens() { 186 while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos; 187 return pos < cc.length; 188 } 189 190 /** 191 * @return the next token if there is one, otherwise null 192 */ 193 //@ public normal_behavior 194 //@ requires moreTokens(); 195 //@ modifies pos; 196 //@ ensures \result != null; 197 //@ also public normal_behavior 198 //@ requires !moreTokens(); 199 //@ modifies pos; 200 //@ ensures \result == null; 201 public String nextToken() { 202 String res = null; 203 while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos; 204 if (pos == cc.length) return res; 205 int start = pos; 206 if (cc[pos] == '"') { 207 ++pos; 208 while (pos < cc.length && cc[pos] != '"' ) ++pos; 209 if (cc[pos] == '"') ++pos; 210 res = ss.substring(start+1,pos-1); 211 } else if (cc[pos] == '\'') { 212 ++pos; 213 while (pos < cc.length && cc[pos] != '\'' ) ++pos; 214 if (cc[pos] == '\'') ++pos; 215 res = ss.substring(start+1,pos-1); 216 } else { 217 while (pos < cc.length && !Character.isWhitespace(cc[pos])) ++pos; 218 res = ss.substring(start,pos); 219 } 220 return res; 221 } 222 } 223 224 /** Deletes the contents of a directory, including subdirectories. 225 If the second argument is true, the directory itself is deleted as well. 226 227 @param d The directory whose contents are deleted 228 @param removeDirectoryItself if true, the directory itself is deleted 229 230 @return false if something could not be deleted; 231 true if everything was successfully deleted 232 */ 233 //@ requires d != null; 234 static public boolean recursivelyRemoveDirectory(File d, 235 boolean removeDirectoryItself) { 236 if (!d.exists()) return true; 237 boolean success = true; 238 File[] fl = d.listFiles(); 239 if (fl != null) { 240 for (int ff=0; ff<fl.length; ++ff) { 241 if (fl[ff].isDirectory()) { 242 if (!recursivelyRemoveDirectory(fl[ff],true)) 243 success = false; 244 } else { 245 if (!fl[ff].delete()) success = false; 246 } 247 } 248 } 249 if (removeDirectoryItself) { 250 if (!d.delete()) success = false; 251 } 252 return success; 253 } 254 255 /** Reads the contents of the file with the given name, returning a String. 256 This is an optimized version that uses the byte array provided and 257 presumes that the file is shorter than the length of the array. 258 259 @param filename The file to be read 260 @param cb A temporary byte array to speed reading 261 262 @return The contents of the file 263 @throws java.io.IOException 264 */ 265 // FIXME - can we check that the file is too long without losing the efficiency benefits? 266 //@ requires filename != null; 267 //@ requires cb != null; 268 //@ ensures \result != null; 269 static public String readFile(String filename, byte[] cb) 270 throws java.io.IOException { 271 int i = 0; 272 int j = 0; 273 java.io.FileInputStream f = null; 274 try { 275 f = new java.io.FileInputStream(filename); 276 while (j != -1) { 277 i = i + j; 278 j = f.read(cb,i,cb.length-i); 279 } 280 } finally { 281 if (f != null) f.close(); 282 } 283 return new String(cb,0,i); 284 } 285 286 /** 287 * Reads a file, returning a String containing the contents 288 * @param filename the file to be read 289 * @return the contents of the file as a String, or null if the 290 * file could not be read 291 */ 292 //@ requires filename != null; 293 static public String readFileX(String filename) { 294 try { 295 return readFile(filename); 296 } catch (Exception e ) { 297 System.out.println("Could not read file " + filename); 298 // FIXME - need a better way to report and catch errors 299 } 300 return null; 301 } 302 303 /** Reads the contents of the file with the given name, returning a String. 304 This version presumes the file is shorter than an internal buffer. 305 FIXME 306 307 @param filename The file to be read 308 @return The contents of the file 309 @throws IOException 310 */ 311 //@ requires filename != null; 312 //@ ensures \result != null; 313 static public String readFile(String filename) throws java.io.IOException { 314 StringBuffer sb = new StringBuffer(10000); 315 char[] cb = new char[10000]; // This hard-coded value can be anything; 316 // smaller numbers will be less efficient since more reads 317 // may result 318 int j = 0; 319 java.io.FileReader f = null; 320 try { 321 f = new java.io.FileReader(filename); 322 while (j != -1) { 323 j = f.read(cb,0,cb.length); 324 if (j != -1) sb.append(cb,0,j); 325 } 326 } finally { 327 if (f != null) f.close(); 328 } 329 return sb.toString(); 330 } 331 332 /** 333 * Executes the static compile(String[]) method of the given class 334 * @param cls The class whose 'compile' method is be invoked 335 * @param args The String[] argument of the method 336 * @return The standard output and error output of the invocation 337 */ 338 //@ requires cls != null; 339 //@ requires args != null; 340 //@ requires !\nonnullelements(args); 341 static public String executeCompile(Class cls, String[] args) { 342 return executeMethod(cls,"compile",args); 343 } 344 345 /** Finds and executes the method with the given name in the given class; 346 the method must have a single argument of type String[]. The 'args' 347 parameter is supplied to it as its argument. 348 * @param cls The class whose method is to be invoked 349 * @param methodname The method to be invoked 350 * @param args The argument of the method 351 * @return The standard output and error output of the invocation 352 */ 353 //@ requires cls != null; 354 //@ requires methodname != null; 355 //@ requires args != null; 356 //@ requires !\nonnullelements(args); 357 static public String executeMethod(Class cls, String methodname, String[] args) { 358 try { 359 Method method = cls.getMethod(methodname, new Class[] { String[].class }); 360 return executeMethod(method,args); 361 } catch (NoSuchMethodException e) { 362 System.out.println("No method compile in class " + cls); // FIXME - better error return needed 363 e.printStackTrace(); 364 throw new RuntimeException(e.toString()); 365 } 366 } 367 368 /** Calls the given method on the given String[] argument. Any standard 369 output and error output is collected and returned as the String 370 return value. 371 * @param method The static method to be invoked 372 * @param args The argument of the method 373 * @return The standard output and error output of the method 374 */ 375 //@ requires method != null; 376 //@ requires args != null; 377 //@ requires !\nonnullelements(args); 378 static public String executeMethod(Method method, String[] args) { 379 try { 380 ByteArrayOutputStream ba = new ByteArrayOutputStream(10000); // FIXME - hardcoded size 381 PrintStream ps = new PrintStream(ba); 382 Utils.setStreams(ps); 383 boolean b = ((Boolean)(method.invoke(null,new Object[]{args}))).booleanValue(); 384 ps.close(); 385 String s = ba.toString(); 386 return s; 387 } catch (Exception e) { 388 Utils.restoreStreams(); // FIXME - see comment in TestFilesTestSuite.java 389 // Need the above restore before we try to print something 390 System.out.println(e.toString()); // FIXME - need better error handling 391 } finally { 392 Utils.restoreStreams(); 393 } 394 return null; 395 } 396 397 /** The suffix to append to create the golden output filename */ 398 static private final String ORACLE_SUFFIX = "-expected"; 399 400 /** The suffix to append to create the actual output filename */ 401 static private final String SAVED_SUFFIX = "-ckd"; 402 403 /** Compares the given string to the content of the given file using 404 a comparator that ignores platform differences in line-endings. 405 The method has the side effect of saving the string value in a file 406 for later comparison if the string and file are different, and making 407 sure that the actual output file (the -ckd file) is deleted if the 408 string and file are the same. The expected output filename is the 409 rootname + "-expected"; the actual output filename is the rootname + "-ckd". 410 * 411 * @param s the String to compare 412 * @param rootname the prefix of the file name 413 * @return The Diff structure that contains the comparison 414 * @throws java.io.IOException 415 */ 416 static public Diff compareStringToFile(String s, String rootname) 417 throws java.io.IOException { 418 String ss = Utils.readFile(rootname+ORACLE_SUFFIX); 419 Diff df = new Diff( "expected", ss, "actual", s ); 420 421 if (!df.areDifferent()) { 422 // If the two strings match, the test succeeds and we make sure 423 // that there is no -ckd file to confuse anyone. 424 (new File(rootname+SAVED_SUFFIX)).delete(); 425 } else { 426 // If the strings do not match, we save the actual string and 427 // fail the test. 428 FileWriter f = null; 429 try { 430 f = new FileWriter(rootname+SAVED_SUFFIX); 431 f.write(s); 432 } finally { 433 if (f != null) f.close(); 434 } 435 } 436 return df; 437 } 438 439 /** This deletes all files (in the current directory) whose 440 names match the given pattern in a regular-expression sense; 441 however, it is only implemented for patterns consisting of 442 characters and at most one '*', since I'm not going to rewrite 443 an RE library. 444 * @param pattern the pattern to match against filenames 445 */ 446 //@ requires pattern != null; 447 static public void removeFiles(String pattern) { 448 File[] list; 449 int k = pattern.indexOf("*"); 450 if (k == -1) { 451 list = new File[] { new File(pattern) }; 452 } else if (k == 0) { 453 final String s = pattern.substring(1); 454 FileFilter ff = new FileFilter() { 455 public boolean accept(File f) { return f.isFile() && f.getName().endsWith(s); } 456 }; 457 list = (new File(".")).listFiles(ff); 458 } else if (k+1 == pattern.length()) { 459 final String s = pattern.substring(0,k); 460 FileFilter ff = new FileFilter() { 461 public boolean accept(File f) { return f.isFile() && f.getName().startsWith(s); } 462 }; 463 list = (new File(".")).listFiles(ff); 464 } else { 465 final String s = pattern.substring(0,k); 466 final String ss = pattern.substring(k+1); 467 final int j = pattern.length()-1; 468 FileFilter ff = new FileFilter() { 469 public boolean accept(File f) { 470 return f.isFile() && 471 f.getName().length() >= j && 472 f.getName().startsWith(s) && 473 f.getName().endsWith(ss); } 474 }; 475 list = (new File(".")).listFiles(ff); 476 } 477 478 for (int i = 0; i<list.length; ++i) { 479 //System.out.println("DELETING " +list[i].getName()); 480 list[i].delete(); 481 } 482 483 } 484 }