You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DirChanges.java 23KB

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