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

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. }