1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
|
/* *******************************************************************
* Copyright (c) 1999-2001 Xerox Corporation,
* 2002 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xerox/PARC initial implementation
* ******************************************************************/
package org.aspectj.testing.harness.bridge;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.testing.util.TestUtil;
import org.aspectj.testing.xml.IXmlWritable;
import org.aspectj.testing.xml.XMLWriter;
import org.aspectj.util.FileUtil;
import org.aspectj.util.LangUtil;
/**
* Calculate changes in a directory tree.
* This implements two different specification styles:
* <ul>
* <li>Specify files added, removed, updated, and/or a component
* to check any existing files</li>
* <li>Specify expected directory. When complete this checks that
* any files in expected directory are matched in the actual.
* (.class files are dissassembled before comparison.)
* </li>
* </ul>
* Usage:
* <ul>
* <li>Set up with any expected changes and/or an expected directory</li>
* <li>Set up with any file checker</li>
* <li>start(..) before changes.
* This issues messages for any removed files not found,
* which represent an error in the expected changes.</li>
* <li>Do whatever operations will change the directory</li>
* <li>end(..).
* This issues messages for any files not removed, added, or updated,
* and, if any checker was set, any checker messages for matching
* added or updated files</li>
* </ul>
* When comparing directories, this ignores any paths containing "CVS".
*/
public class DirChanges {
public static final String DELAY_NAME = "dir-changes.delay";
private static final long DELAY;
static {
long delay = 10l;
try {
delay = Long.getLong(DELAY_NAME).longValue();
if ((delay > 40000) || (delay < 0)) {
delay = 10l;
}
} catch (Throwable t) {
// ignore
}
DELAY = delay;
}
private static final boolean EXISTS = true;
final Spec spec;
/** start time, in milliseconds - valid only from start(..)..end(..) */
long startTime;
/** base directory of actual files - valid only from start(..)..end(..) */
File baseDir;
/** if set, this is run against any resulting existing files
* specified in added/updated lists.
* This does not affect expected-directory comparison.
*/
IFileChecker fileChecker;
/** handler valid from start..end of start(..) and end(..) methods */
IMessageHandler handler;
/**
* Constructor for DirChanges.
*/
public DirChanges(Spec spec) {
LangUtil.throwIaxIfNull(spec, "spec");
this.spec = spec;
}
/**
* Inspect the base dir, and issue any messages for
* removed files not present.
* @param baseDir File for a readable directory
* @return true if this started without sending messages
* for removed files not present.
*/
public boolean start(IMessageHandler handler, File baseDir) {
FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir");
final IMessageHandler oldHandler = this.handler;
this.handler = handler;
this.baseDir = baseDir;
startTime = 0l;
final boolean doCompare = false;
boolean result
= exists("at start, did not expect added file to exist", !EXISTS, spec.added, doCompare);
result &= exists("at start, expected unchanged file to exist", EXISTS, spec.unchanged, doCompare);
result &= exists("at start, expected updated file to exist", EXISTS, spec.updated, doCompare);
result &= exists("at start, expected removed file to exist", EXISTS, spec.removed, doCompare);
startTime = System.currentTimeMillis();
// ensure tests don't complete in < 1 second, otherwise can confuse fast machines.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.handler = oldHandler;
return result;
}
/**
* Inspect the base dir, issue any messages for
* files not added, files not updated, and files not removed,
* and compare expected/actual files added or updated.
* This sleeps before checking until at least DELAY milliseconds after start.
* @throws IllegalStateException if called before start(..)
*/
public boolean end(IMessageHandler handler, File srcBaseDir) {
FileUtil.throwIaxUnlessCanReadDir(baseDir, "baseDir");
if (0l == startTime) {
throw new IllegalStateException("called before start");
}
final long targetTime = startTime + spec.delayInMilliseconds;
do {
long curTime = System.currentTimeMillis();
if (curTime >= targetTime) {
break;
}
try {
Thread.sleep(targetTime-curTime);
} catch (InterruptedException e) {
break;
}
} while (true);
final IMessageHandler oldHandler = this.handler;
this.handler = handler;
try {
// variant 1: check specified files
// deferring comparison to end...
final boolean doCompare = (null != fileChecker);
final boolean fastFail = spec.fastFail;
boolean result
= exists("at end, expected file was not added", EXISTS, spec.added, doCompare);
if (result || !fastFail) {
result &= exists("at end, expected file was not unchanged", EXISTS, spec.unchanged, doCompare, false);
}
if (result || !fastFail) {
result &= exists("at end, expected file was not updated", EXISTS, spec.updated, doCompare);
}
if (result || !fastFail) {
result &= exists("at end, file exists, was not removed", !EXISTS, spec.removed, doCompare);
}
// if (result || !fastFail) {
// // XXX validate that unchanged mod-time did not change
// }
// variant 1: compare expected directory
if (result || !fastFail) {
result &= compareDir(srcBaseDir);
}
return result;
} finally {
this.handler = oldHandler;
baseDir = null;
startTime = 0l;
}
}
/**
* Verify that all files in any specified expected directory
* have matching files in the base directory, putting any messages
* in the handler (only one if the spec indicates fast-fail).
* @param srcBaseDir the File for the base directory of the test sources
* (any expected dir is specified relative to this directory)
* @return true if the same, false otherwise
*/
private boolean compareDir(File srcBaseDir) {
if (null == spec.expDir) {
return true;
}
File expDir = new File(srcBaseDir, spec.expDir);
File actDir = baseDir;
//System.err.println("XXX comparing actDir=" + actDir + " expDir=" + expDir);
return TestUtil.sameDirectoryContents(handler, expDir, actDir, spec.fastFail);
}
/** @param comp FileMessageComparer (if any) given matching messages to compare */
protected void setFileComparer(IFileChecker comp) {
this.fileChecker = comp;
}
/**
* Signal fail if any files do {not} exist or do {not} have last-mod-time after startTime
* @param handler the IMessageHandler sink for messages
* @param label the String infix for the fail message
* @param exists if true, then file must exist and be modified after start time;
* if false, then the file must not exist or must be modified before start time.
* @param pathList the List of path (without any Spec.defaultSuffix) of File
* in Spec.baseDir to find (or not, if !exists)
*/
protected boolean exists(
String label,
boolean exists,
List pathList,
boolean doCompare) {
// boolean expectStartEarlier = true;
return exists(label, exists, pathList,doCompare, true);
}
protected boolean exists(
String label,
boolean exists,
List pathList,
boolean doCompare,
boolean expectStartEarlier) {
boolean result = true;
if (!LangUtil.isEmpty(pathList)) {
// final File expDir = ((!doCompare || (null == spec.expDir))
// ? null
// : new File(baseDir, spec.expDir));
for (Iterator iter = pathList.iterator(); iter.hasNext();) {
final String entry = (String) iter.next() ;
String path = entry ;
if (null != spec.defaultSuffix) {
if (".class".equals(spec.defaultSuffix)) {
path = path.replace('.', '/');
}
path = path + spec.defaultSuffix;
}
File actualFile = new File(baseDir, path);
if (exists != (actualFile.canRead() && actualFile.isFile()
&& (expectStartEarlier
? startTime <= actualFile.lastModified()
: startTime > actualFile.lastModified()
))) {
failMessage(handler, exists, label, path, actualFile);
if (result) {
result = false;
}
} else if (exists && doCompare && (null != fileChecker)) {
if (!fileChecker.checkFile(handler, path, actualFile) && result) {
result = false;
}
}
}
}
return result;
}
/**
* Generate fail message "{un}expected {label} file {path} in {baseDir}".
* @param handler the IMessageHandler sink
* @param label String message infix
* @param path the path to the file
*/
protected void failMessage(
IMessageHandler handler,
boolean exists,
String label,
String path,
File file) {
MessageUtil.fail(handler, label + " \"" + path + "\" in " + baseDir);
}
/** Check actual File found at a path, usually to diff expected/actual contents */
public static interface IFileChecker {
/**
* Check file found at path.
* Implementations should return false when adding fail (or worse)
* message to the handler, and true otherwise.
* @param handler IMessageHandler sink for messages, esp. fail.
* @param path String for normalized path portion of actualFile.getPath()
* @param actualFile File to file found
* @return false if check failed and messages added to handler
*/
boolean checkFile(IMessageHandler handler, String path, File actualFile);
}
// File-comparison code with a bit more generality -- too unweildy
// /**
// * Default FileChecker compares files literally, transforming any
// * with registered normalizers.
// */
// public static class FileChecker implements IFileChecker {
// final File baseExpectedDir;
// NormalizedCompareFiles fileComparer;
//
// public FileChecker(File baseExpectedDir) {
// this.baseExpectedDir = baseExpectedDir;
// fileComparer = new NormalizedCompareFiles();
// }
// public boolean checkFile(IMessageHandler handler, String path, File actualFile) {
// if (null == baseExpectedDir) {
// MessageUtil.error(handler, "null baseExpectedDir set on construction");
// } else if (!baseExpectedDir.canRead() || !baseExpectedDir.isDirectory()) {
// MessageUtil.error(handler, "bad baseExpectedDir: " + baseExpectedDir);
// } else {
// File expectedFile = new File(baseExpectedDir, path);
// if (!expectedFile.canRead()) {
// MessageUtil.fail(handler, "cannot read expected file: " + expectedFile);
// } else {
// return doCheckFile(handler, expectedFile, actualFile, path);
// }
// }
// return false;
// }
//
// protected boolean doCheckFile(
// IMessageHandler handler,
// File expectedFile,
// File actualFile,
// String path) {
// fileComparer.setHandler(handler);
// FileLine[] expected = fileComparer.diff();
// return false;
// }
// }
// /**
// * CompareFiles implementation that pre-processes input
// * to normalize it. Currently it reads all files except
// * .class files, which it disassembles first.
// */
// public static class NormalizedCompareFiles extends CompareFiles {
// private final static String[] NO_PATHS = new String[0];
// private static String normalPath(File file) { // XXX util
// if (null == file) {
// return "";
// }
// return file.getAbsolutePath().replace('\\', '/');
// }
//
// private String[] baseDirs;
// private IMessageHandler handler;
//
// public NormalizedCompareFiles() {
// }
//
// void init(IMessageHandler handler, File[] baseDirs) {
// this.handler = handler;
// if (null == baseDirs) {
// this.baseDirs = NO_PATHS;
// } else {
// this.baseDirs = new String[baseDirs.length];
// for (int i = 0; i < baseDirs.length; i++) {
// this.baseDirs[i] = normalPath(baseDirs[i]) + "/";
// }
// }
// }
//
// private String getClassName(File file) {
// String result = null;
// String path = normalPath(file);
// if (!path.endsWith(".class")) {
// MessageUtil.error(handler,
// "NormalizedCompareFiles expected "
// + file
// + " to end with .class");
// } else {
// path = path.substring(0, path.length()-6);
// for (int i = 0; i < baseDirs.length; i++) {
// if (path.startsWith(baseDirs[i])) {
// return path.substring(baseDirs[i].length()).replace('/', '.');
// }
// }
// MessageUtil.error(handler,
// "NormalizedCompareFiles expected "
// + file
// + " to start with one of "
// + LangUtil.arrayAsList(baseDirs));
// }
//
// return result;
// }
//
// /**
// * Read file as normalized lines, sending handler any messages
// * ERROR for input failures and FAIL for processing failures.
// * @return NOLINES on error or normalized lines from file otherwise
// */
// public FileLine[] getFileLines(File file) {
// FileLineator capture = new FileLineator();
// InputStream in = null;
// try {
// if (!file.getPath().endsWith(".class")) {
// in = new FileInputStream(file);
// FileUtil.copyStream(
// new BufferedReader(new InputStreamReader(in)),
// new PrintWriter(capture));
// } else {
// String name = getClassName(file);
// if (null == name) {
// return new FileLine[0];
// }
// String path = normalPath(file);
// path = path.substring(0, path.length()-name.length());
// // XXX sole dependency on bcweaver/bcel
// LazyClassGen.disassemble(path, name, capture);
// }
// } catch (IOException e) {
// MessageUtil.fail(handler,
// "NormalizedCompareFiles IOException reading " + file, e);
// return null;
// } finally {
// if (null != in) {
// try { in.close(); }
// catch (IOException e) {} // ignore
// }
// capture.flush();
// capture.close();
// }
// String missed = capture.getMissed();
// if (!LangUtil.isEmpty(missed)) {
// MessageUtil.fail(handler,
// "NormalizedCompareFiles missed input: "
// + missed);
// return null;
// } else {
// return capture.getFileLines();
// }
// }
//
//
// }
/**
* Specification for a set of File added, removed, or updated
* in a given directory, or for a directory base for a tree of expected files.
* If defaultSuffix is specified, entries may be added without it.
* Currently the directory tree
* only is used to verify files that are expected
* and found after the process completes.
*/
public static class Spec implements IXmlWritable {
/** XML element name */
public static final String XMLNAME = "dir-changes";
/** a symbolic name for the base directory */
String dirToken; // XXX default to class?
/** if set, then append to specified paths when seeking files */
String defaultSuffix;
/** relative path of dir with expected files for comparison */
String expDir;
long delayInMilliseconds = DELAY;
/** if true, fail on first mis-match */
boolean fastFail;
/** relative paths (String) of expected files added */
final ArrayList added;
/** relative paths (String) of expected files removed/deleted */
final ArrayList removed;
/** relative paths (String) of expected files updated/changed */
final ArrayList updated;
/** relative paths (String) of expected files NOT
* added, removed, or changed
* XXX unchanged unimplemented
*/
final ArrayList unchanged;
public Spec() {
added = new ArrayList();
removed = new ArrayList();
updated = new ArrayList();
unchanged = new ArrayList();
}
/**
* @param dirToken the symbol name of the base directory (classes, run)
*/
public void setDirToken(String dirToken) {
this.dirToken = dirToken;
}
/**
* Set the directory containing the expected files.
* @param expectedDirRelativePath path relative to the test base
* of the directory containing expected results for the output dir.
*/
public void setExpDir(String expectedDirRelativePath) {
expDir = expectedDirRelativePath;
}
public void setDelay(String delay) {
if (null != delay) {
// let NumberFormatException propogate up
delayInMilliseconds = Long.parseLong(delay);
if (delayInMilliseconds < 0l) {
delayInMilliseconds = 0l;
}
}
}
/**
* @param clipSuffix the String suffix, if any, to clip automatically
*/
public void setDefaultSuffix(String defaultSuffix) {
this.defaultSuffix = defaultSuffix;
}
public void setAdded(String items) {
XMLWriter.addFlattenedItems(added, items);
}
public void setRemoved(String items) {
XMLWriter.addFlattenedItems(removed, items);
}
public void setUpdated(String items) {
XMLWriter.addFlattenedItems(updated, items);
}
public void setUnchanged(String items) {
XMLWriter.addFlattenedItems(unchanged, items);
}
public void setFastfail(boolean fastFail) {
this.fastFail = fastFail;
}
/** @return true if some list was specified */
private boolean hasFileList() {
return (!LangUtil.isEmpty(added)
|| !LangUtil.isEmpty(removed)
|| !LangUtil.isEmpty(updated)
|| !LangUtil.isEmpty(unchanged)
);
}
/**
* Emit specification in XML form if not empty.
* This writes nothing if there is no expected dir
* and there are no added, removed, or changed.
* fastFail is written only if true, since the default is false.
*/
public void writeXml(XMLWriter out) {
if (!hasFileList() && LangUtil.isEmpty(expDir)) {
return;
}
// XXX need to permit defaults here...
out.startElement(XMLNAME, false);
if (!LangUtil.isEmpty(dirToken)) {
out.printAttribute("dirToken", dirToken.trim());
}
if (!LangUtil.isEmpty(defaultSuffix)) {
out.printAttribute("defaultSuffix", defaultSuffix.trim());
}
if (!LangUtil.isEmpty(expDir)) {
out.printAttribute("expDir", expDir.trim());
}
if (!LangUtil.isEmpty(added)) {
out.printAttribute("added", XMLWriter.flattenList(added));
}
if (!LangUtil.isEmpty(removed)) {
out.printAttribute("removed", XMLWriter.flattenList(removed));
}
if (!LangUtil.isEmpty(updated)) {
out.printAttribute("updated", XMLWriter.flattenList(updated));
}
if (!LangUtil.isEmpty(unchanged)) {
out.printAttribute("unchanged", XMLWriter.flattenList(unchanged));
}
if (fastFail) {
out.printAttribute("fastFail", "true");
}
out.endElement(XMLNAME);
}
/**
* Write list as elements to XMLWriter.
* @param out XMLWriter output sink
* @param dirChanges List of DirChanges.Spec to write
*/
public static void writeXml(XMLWriter out, List dirChanges) {
if (LangUtil.isEmpty(dirChanges)) {
return;
}
LangUtil.throwIaxIfNull(out, "out");
for (Iterator iter = dirChanges.iterator(); iter.hasNext();) {
DirChanges.Spec spec = (DirChanges.Spec) iter.next();
if (null == spec) {
continue;
}
spec.writeXml(out);
}
}
} // class Spec
}
|