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.13 2006/08/13 02:36:41 chalin 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 /*@ public normal_behavior
056 @ requires disable;
057 @ assignable \nothing;
058 @ also
059 @ public normal_behavior
060 @ requires !disable;
061 @ requires ps.isOpen;
062 @ assignable System.out, System.err;
063 @ ensures System.out == ps
064 @ && System.err == ps;
065 @*/
066 static public void setStreams(/*@ non_null */ PrintStream ps) {
067 if (disable) return;
068 System.out.flush();
069 System.err.flush();
070 System.setOut(ps);
071 System.setErr(ps);
072 }
073
074 /** Creates a new output stream (which is returned) and makes it
075 the stream into which the standard and error outputs are captured.
076
077 @return an output stream into which standard and error output
078 is captured
079 */
080 /*@ public normal_behavior
081 @ requires disable;
082 @ assignable \nothing;
083 @ ensures \fresh(\result);
084 @ also
085 @ public normal_behavior
086 @ requires !disable;
087 @ assignable System.out, System.err;
088 @ ensures \fresh(\result);
089 @ ensures \result.isOpen;
090 @ ensures System.out.underlyingStream == \result;
091 @ ensures System.err.underlyingStream == \result;
092 @ ensures System.err == System.out;
093 @*/
094 static public /*@ non_null */ ByteArrayOutputStream setStreams() {
095 ByteArrayOutputStream ba = new ByteArrayOutputStream(10000);
096 PrintStream ps = new PrintStream(ba);
097 setStreams(ps);
098 return ba;
099 }
100 // TODO - note the hard-coded size of the stream above. It needs to be
101 // variable, or at least be sure to capture overflows. The size needs to
102 // be large enough to hold the output of a test.
103
104 /** Restores System.out and System.err to the initial,
105 system-defined values. It is ok to call this method
106 even if setStreams has not been called.
107 <p>
108 Note that setStreams/restoreStreams operate on the global
109 values of System.out and System.err; these implementations
110 are not synchronized - you will need to take care of any
111 race conditions if you utilize these in more than one thread.
112 */
113 /*@ public normal_behavior
114 @ requires disable;
115 @ assignable \nothing;
116 @ also
117 @ public normal_behavior
118 @ requires !disable;
119 @ assignable System.out, System.err;
120 @ ensures System.out == pso
121 @ && System.err == pse;
122 @*/
123 static public void restoreStreams() {
124 restoreStreams(false);
125 }
126
127 /** Restores System.out and System.err to the initial,
128 system-defined values. It is ok to call this method
129 even if setStreams has not been called.
130 <p>
131 Note that setStreams/restoreStreams operate on the global
132 values of System.out and System.err; these implementations
133 are not synchronized - you will need to take care of any
134 race conditions if you utilize these in more than one thread.
135
136 * @param close if true, the current output and error streams
137 * are closed before being reset (if they are not currently the
138 * System output and error streams)
139 */
140 /*@ public normal_behavior
141 @ requires disable;
142 @ assignable \nothing;
143 @ also
144 @ public normal_behavior
145 @ requires !disable;
146 @ assignable System.out, System.err;
147 @ ensures System.out == pso
148 @ && System.err == pse;
149 @ ensures (close && \old(System.out) != pso) ==> \old(System.out).wasClosed;
150 @ ensures (close && \old(System.err) != pse) ==> \old(System.err).wasClosed;
151 @*/
152 static public void restoreStreams(boolean close) {
153 if (disable) return;
154 if (close) {
155 if (pso != System.out) System.out.close();
156 if (pse != System.err) System.err.close();
157 }
158 System.setOut(pso);
159 System.setErr(pse);
160 }
161
162
163
164 /** Parses a string into arguments as if it were a command-line, using
165 the QuoteTokenizer to parse the tokens.
166
167 @param s The String to parse
168 @return The input string parsed into command-line arguments
169 */
170 //@ ensures \nonnullelements(\result);
171 static public /*@ non_null */ String[] parseLine(/*@ non_null */String s) {
172 QuoteTokenizer q = new QuoteTokenizer(s);
173 java.util.ArrayList args = new java.util.ArrayList();
174 while (q.hasMoreTokens()) {
175 String a = q.nextToken();
176 args.add(a);
177 }
178 return (String[])args.toArray(new String[args.size()]);
179 }
180
181 /** An enumerator that parses a string into tokens, according to the
182 rules a command-line would use. White space separates tokens,
183 with double-quoted and single-quoted strings recognized.
184 */
185 // FIXME - does not handle escape sequences in strings, nor
186 // quoted strings that are not the whole token, e.g.
187 // a b c+"asd" should be three tokens, "a", "b", and c+"asd" .
188 static public class QuoteTokenizer {
189 /** The String being tokenized */
190 /*@ non_null spec_public */ final private String ss;
191
192 /** A char array representation of the String being tokenized */
193 /*@ non_null spec_public */ final private char[] cc;
194
195 /** The position in the char array */
196 /*@ spec_public */ private int pos = 0;
197 /*@ invariant pos >= 0;
198 @ invariant pos <= cc.length;
199 @*/
200
201 /** Initializes the tokenizer with the given String
202 * @param s the String to be tokenized
203 */
204 //@ modifies ss,cc;
205 //@ ensures s == ss;
206 public QuoteTokenizer(/*@ non_null */ String s) {
207 ss = s;
208 cc = s.toCharArray();
209 }
210
211 /*@ public model boolean moreTokens;
212 @ public represents moreTokens <-
213 @ (\exists int i; pos <= i && i < cc.length;
214 @ Character.isWhitespace(cc[i]));
215 @*/
216
217 /*@ public model boolean moreChar;
218 @ public represents moreChar <- pos < cc.length;
219 @*/
220
221 /*@ public invariant_redundantly moreTokens ==> moreChar;
222 @ public invariant_redundantly
223 @ moreChar && !Character.isWhitespace(cc[pos]) ==> moreTokens;
224 @*/
225
226 /**
227 * @return true if there are more tokens to be returned
228 */
229 /*@ public normal_behavior
230 @ modifies pos;
231 @ ensures \result == moreChar;
232 @ ensures \result ==> !Character.isWhitespace(cc[pos]);
233 @ ensures_redundantly \result ==> moreTokens; */
234 /*+@ ensures \result == moreTokens; // true but esc cannot prove
235 @ ensures moreTokens == \old(moreTokens); // true but esc cannot prove
236 @*/
237 public boolean hasMoreTokens() {
238 while (pos < cc.length && Character.isWhitespace(cc[pos])) pos++;
239 return pos < cc.length;
240 }
241
242 /**
243 * @return the next token if there is one, otherwise null
244 */
245 /*@ public normal_behavior
246 @ requires moreChar;
247 @ modifies pos;
248 @ ensures pos > \old(pos);
249 @ also public normal_behavior
250 @ requires !moreChar;
251 @ modifies \nothing;
252 @ ensures \result == null;
253 @*/
254 /*+@ also
255 @ public normal_behavior
256 @ requires moreTokens;
257 @ modifies pos;
258 @ ensures \result != null;
259 @ also public normal_behavior
260 @ requires !moreTokens;
261 @ modifies pos;
262 @ ensures \result == null;
263 @*/
264 public String nextToken() {
265 String res = null;
266 while (pos < cc.length && Character.isWhitespace(cc[pos])) ++pos;
267 if (pos == cc.length) return res;
268 int start = pos;
269 if (cc[pos] == '"') {
270 ++pos;
271 while (pos < cc.length && cc[pos] != '"' ) ++pos;
272 if (cc[pos] == '"') ++pos;
273 res = ss.substring(start+1,pos-1);
274 } else if (cc[pos] == '\'') {
275 ++pos;
276 while (pos < cc.length && cc[pos] != '\'' ) ++pos;
277 if (cc[pos] == '\'') ++pos;
278 res = ss.substring(start+1,pos-1);
279 } else {
280 while (pos < cc.length && !Character.isWhitespace(cc[pos])) ++pos;
281 res = ss.substring(start,pos);
282 }
283 return res;
284 }
285 }
286
287 /** Deletes the contents of a directory, including subdirectories.
288 If the second argument is true, the directory itself is deleted as well.
289
290 @param d The directory whose contents are deleted
291 @param removeDirectoryItself if true, the directory itself is deleted
292
293 @return false if something could not be deleted;
294 true if everything was successfully deleted
295 */
296 static public boolean recursivelyRemoveDirectory(/*@ non_null */ File d,
297 boolean removeDirectoryItself) {
298 if (!d.exists()) return true;
299 boolean success = true;
300 File[] fl = d.listFiles();
301 if (fl != null) {
302 for (int ff=0; ff<fl.length; ++ff) {
303 if (fl[ff].isDirectory()) {
304 if (!recursivelyRemoveDirectory(fl[ff],true))
305 success = false;
306 } else {
307 if (!fl[ff].delete()) success = false;
308 }
309 }
310 }
311 if (removeDirectoryItself) {
312 if (!d.delete()) success = false;
313 }
314 return success;
315 }
316
317 /** Reads the contents of the file with the given name, returning a String.
318 This is an optimized version that uses the byte array provided and
319 presumes that the file is shorter than the length of the array.
320
321 @param filename The file to be read
322 @param cb A temporary byte array to speed reading
323
324 @return The contents of the file
325 @throws java.io.IOException
326 */
327 // FIXME - can we check that the file is too long without losing the efficiency benefits?
328 static public /*@ non_null */ String readFile(/*@ non_null */ String filename, /*@ non_null */ byte[] cb)
329 throws java.io.IOException {
330 int i = 0;
331 int j = 0;
332 java.io.FileInputStream f = null;
333 try {
334 f = new java.io.FileInputStream(filename);
335 while (j != -1) {
336 i = i + j;
337 j = f.read(cb,i,cb.length-i);
338 }
339 } finally {
340 if (f != null) f.close();
341 }
342 return new String(cb,0,i);
343 }
344
345 /**
346 * Reads a file, returning a String containing the contents
347 * @param filename the file to be read
348 * @return the contents of the file as a String, or null if the
349 * file could not be read
350 */
351 static public String readFileX(/*@ non_null */ String filename) {
352 try {
353 return readFile(filename);
354 } catch (Exception e ) {
355 System.out.println("Could not read file " + filename);
356 // FIXME - need a better way to report and catch errors
357 }
358 return null;
359 }
360
361 /** Reads the contents of the file with the given name, returning a String.
362 This version presumes the file is shorter than an internal buffer.
363 FIXME
364
365 @param filename The file to be read
366 @return The contents of the file
367 @throws IOException
368 */
369 static public /*@ non_null */ String readFile(/*@ non_null */ String filename) throws java.io.IOException {
370 StringBuffer sb = new StringBuffer(10000);
371 char[] cb = new char[10000]; // This hard-coded value can be anything;
372 // smaller numbers will be less efficient since more reads
373 // may result
374 int j = 0;
375 java.io.FileReader f = null;
376 try {
377 f = new java.io.FileReader(filename);
378 while (j != -1) {
379 j = f.read(cb,0,cb.length);
380 if (j != -1) sb.append(cb,0,j);
381 }
382 } finally {
383 if (f != null) f.close();
384 }
385 return sb.toString();
386 }
387
388 /**
389 * Executes the static compile(String[]) method of the given class
390 * @param cls The class whose 'compile' method is be invoked
391 * @param args The String[] argument of the method
392 * @return The standard output and error output of the invocation
393 */
394 //@ requires \nonnullelements(args);
395 static public String executeCompile(/*@ non_null */ Class cls,
396 /*@ non_null */ String[] args)
397 throws SecurityException, NoSuchMethodException
398 {
399 return executeMethod(cls,"compile",args);
400 }
401
402 /** Finds and executes the method with the given name in the given class;
403 the method must have a single argument of type String[]. The 'args'
404 parameter is supplied to it as its argument.
405 * @param cls The class whose method is to be invoked
406 * @param methodname The method to be invoked
407 * @param args The argument of the method
408 * @return The standard output and error output of the invocation
409 */
410 //@ requires \nonnullelements(args);
411 static private String executeMethod(/*@ non_null */ Class cls,
412 /*@ non_null */ String methodname,
413 /*@ non_null */ String[] args)
414 throws SecurityException, NoSuchMethodException
415 {
416 try {
417 Method method = cls.getMethod(methodname, new Class[] { String[].class });
418 return executeMethod(method,args);
419 } catch (NoSuchMethodException e) {
420 System.err.println("No method compile in class " + cls); // FIXME - better error return needed
421 e.printStackTrace();
422 throw e; // new RuntimeException(e.toString());
423 }
424 }
425
426 /** Calls the given method on the given String[] argument. Any standard
427 output and error output is collected and returned as the String
428 return value.
429 * @param method The static method to be invoked
430 * @param args The argument of the method
431 * @return The standard output and error output of the method
432 */
433 //@ requires \nonnullelements(args);
434 static private String executeMethod(/*@ non_null */ Method method,
435 /*@ non_null */ String[] args) {
436 try {
437 ByteArrayOutputStream ba = setStreams();
438 Object result = method.invoke(null,new Object[]{args});
439 // The following line might cause a cast error, but since b is not used it is comment out.
440 // boolean b = ((Boolean)result).booleanValue();
441 return ba.toString();
442 } catch (Exception e) {
443 Utils.restoreStreams(); // FIXME - see comment in TestFilesTestSuite.java
444 // Need the above restore before we try to print something
445 System.out.println(e.toString()); // FIXME - need better error handling
446 } finally {
447 Utils.restoreStreams();
448 }
449 return null;
450 }
451
452 /** The suffix to append to create the golden output filename */
453 static private final String ORACLE_SUFFIX = "-expected";
454
455 /** The suffix to append to create the actual output filename */
456 static private final String SAVED_SUFFIX = "-ckd";
457
458 /** Compares the given string to the content of the given file using
459 a comparator that ignores platform differences in line-endings.
460 The method has the side effect of saving the string value in a file
461 for later comparison if the string and file are different, and making
462 sure that the actual output file (the -ckd file) is deleted if the
463 string and file are the same. The expected output filename is the
464 rootname + "-expected"; the actual output filename is the rootname + "-ckd".
465 *
466 * @param s the String to compare
467 * @param rootname the prefix of the file name
468 * @return The Diff structure that contains the comparison
469 * @throws java.io.IOException
470 */
471 static public Diff compareStringToFile(/*@ non_null */ String s, String rootname)
472 throws java.io.IOException {
473 String ss = Utils.readFile(rootname+ORACLE_SUFFIX);
474 Diff df = new Diff( "expected", ss, "actual", s );
475
476 if (!df.areDifferent()) {
477 // If the two strings match, the test succeeds and we make sure
478 // that there is no -ckd file to confuse anyone.
479 (new File(rootname+SAVED_SUFFIX)).delete();
480 } else {
481 // If the strings do not match, we save the actual string and
482 // fail the test.
483 FileWriter f = null;
484 try {
485 f = new FileWriter(rootname+SAVED_SUFFIX);
486 f.write(s);
487 } finally {
488 if (f != null) f.close();
489 }
490 }
491 return df;
492 }
493
494 /** This deletes all files (in the current directory) whose
495 names match the given pattern in a regular-expression sense;
496 however, it is only implemented for patterns consisting of
497 characters and at most one '*', since I'm not going to rewrite
498 an RE library.
499 * @param pattern the pattern to match against filenames
500 */
501 static public void removeFiles(/*@ non_null */ String pattern) {
502 File[] list;
503 int k = pattern.indexOf("*");
504 if (k == -1) {
505 list = new File[] { new File(pattern) };
506 } else if (k == 0) {
507 final String s = pattern.substring(1);
508 FileFilter ff = new FileFilter() {
509 public boolean accept(File f) { return f.isFile() && f.getName().endsWith(s); }
510 };
511 list = (new File(".")).listFiles(ff);
512 } else if (k+1 == pattern.length()) {
513 final String s = pattern.substring(0,k);
514 FileFilter ff = new FileFilter() {
515 public boolean accept(File f) { return f.isFile() && f.getName().startsWith(s); }
516 };
517 list = (new File(".")).listFiles(ff);
518 } else {
519 final String s = pattern.substring(0,k);
520 final String ss = pattern.substring(k+1);
521 final int j = pattern.length()-1;
522 FileFilter ff = new FileFilter() {
523 public boolean accept(File f) {
524 return f.isFile() &&
525 f.getName().length() >= j &&
526 f.getName().startsWith(s) &&
527 f.getName().endsWith(ss); }
528 };
529 list = (new File(".")).listFiles(ff);
530 }
531
532 for (int i = 0; i<list.length; ++i) {
533 //System.out.println("DELETING " +list[i].getName());
534 list[i].delete();
535 }
536
537 }
538 }