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.

TestUtil.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /* *******************************************************************
  2. * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
  3. * All rights reserved.
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Common Public License v1.0
  6. * which accompanies this distribution and is available at
  7. * http://www.eclipse.org/legal/cpl-v10.html
  8. *
  9. * Contributors:
  10. * Xerox/PARC initial implementation
  11. * ******************************************************************/
  12. package org.aspectj.testing.util;
  13. import org.aspectj.bridge.IMessageHandler;
  14. import org.aspectj.bridge.MessageUtil;
  15. import org.aspectj.util.FileUtil;
  16. import org.aspectj.util.LangUtil;
  17. import org.aspectj.util.Reflection;
  18. import java.io.BufferedReader;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.DataInputStream;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.PrintStream;
  26. import java.io.StringReader;
  27. import java.io.StringWriter;
  28. import java.lang.reflect.InvocationTargetException;
  29. import java.lang.reflect.Method;
  30. import java.util.ArrayList;
  31. import java.util.Arrays;
  32. import java.util.Collection;
  33. import java.util.HashSet;
  34. import java.util.List;
  35. import java.util.Set;
  36. import jdiff.text.FileLine;
  37. import jdiff.util.Diff;
  38. import jdiff.util.DiffNormalOutput;
  39. import junit.framework.Assert;
  40. import junit.framework.TestCase;
  41. import junit.runner.TestCaseClassLoader;
  42. /**
  43. * Things that junit should perhaps have, but doesn't.
  44. * Note the file-comparison methods require JDiff to run,
  45. * but JDiff types are not required to resolve this class.
  46. * Also, the bytecode weaver is required to compare class
  47. * files, but not to compare other files.
  48. */
  49. public final class TestUtil {
  50. private TestUtil() {
  51. super();
  52. }
  53. // ---- arrays
  54. public static void assertArrayEquals(String msg, Object[] expected, Object[] found) {
  55. TestCase.assertEquals(msg, Arrays.asList(expected), Arrays.asList(found));
  56. }
  57. // ---- unordered
  58. public static void assertSetEquals(Collection expected, Collection found) {
  59. assertSetEquals(null, expected, found);
  60. }
  61. public static void assertSetEquals(String msg, Object[] expected, Object[] found) {
  62. assertSetEquals(msg, Arrays.asList(expected), Arrays.asList(found));
  63. }
  64. public static void assertSetEquals(
  65. String msg,
  66. Collection expected,
  67. Collection found) {
  68. msg = (msg == null) ? "" : msg + ": ";
  69. Set results1 = new HashSet(found);
  70. results1.removeAll(expected);
  71. Set results2 = new HashSet(expected);
  72. results2.removeAll(found);
  73. if (results1.isEmpty()) {
  74. TestCase.assertTrue(
  75. msg + "Expected but didn't find: " + results2.toString(),
  76. results2.isEmpty());
  77. } else if (results2.isEmpty()) {
  78. TestCase.assertTrue(
  79. msg + "Didn't expect: " + results1.toString(),
  80. results1.isEmpty());
  81. } else {
  82. TestCase.assertTrue(
  83. msg
  84. + "Expected but didn't find: "
  85. + results2.toString()
  86. + "\nDidn't expect: "
  87. + results1.toString(),
  88. false);
  89. }
  90. }
  91. // ---- objects
  92. public static void assertCommutativeEquals(Object a, Object b, boolean should) {
  93. TestCase.assertEquals(a + " equals " + b, should, a.equals(b));
  94. TestCase.assertEquals(b + " equals " + a, should, b.equals(a));
  95. assertHashEquals(a, b, should);
  96. }
  97. private static void assertHashEquals(Object s, Object t, boolean should) {
  98. if (should) {
  99. TestCase.assertTrue(
  100. s + " does not hash to same as " + t,
  101. s.hashCode() == t.hashCode());
  102. } else {
  103. if (s.hashCode() == t.hashCode()) {
  104. System.err.println("warning: hash collision with hash = " + t.hashCode());
  105. System.err.println(" for " + s);
  106. System.err.println(" and " + t);
  107. }
  108. }
  109. }
  110. // -- reflective stuff
  111. public static void runMain(String classPath, String className) {
  112. runMethod(classPath, className, "main", new Object[] { new String[0] });
  113. }
  114. public static Object runMethod(String classPath, String className, String methodName, Object[] args) {
  115. classPath += File.pathSeparator + System.getProperty("java.class.path");
  116. ClassLoader loader = new TestCaseClassLoader(classPath);
  117. Class c=null;
  118. try {
  119. c = loader.loadClass(className);
  120. } catch (ClassNotFoundException e) {
  121. Assert.assertTrue("unexpected exception: " + e, false);
  122. }
  123. return Reflection.invokestaticN(c, methodName, args);
  124. }
  125. /**
  126. * Checks that two multi-line strings have the same value.
  127. * Each line is trimmed before comparision
  128. * Produces an error on the particular line of conflict
  129. */
  130. public static void assertMultiLineStringEquals(String message, String s1, String s2) {
  131. try {
  132. BufferedReader r1 = new BufferedReader(new StringReader(s1));
  133. BufferedReader r2 = new BufferedReader(new StringReader(s2));
  134. List lines = new ArrayList();
  135. String l1, l2;
  136. int index = 1;
  137. while(true) {
  138. l1 = readNonBlankLine(r1);
  139. l2 = readNonBlankLine(r2);
  140. if (l1 == null || l2 == null) break;
  141. if (l1.equals(l2)) {
  142. lines.add(l1);
  143. } else {
  144. showContext(lines);
  145. Assert.assertEquals(message +"(line " + index +")", l1, l2);
  146. }
  147. index++;
  148. }
  149. if (l1 != null) showContext(lines);
  150. Assert.assertTrue(message + ": unexpected " + l1, l1 == null);
  151. if (l2 != null) showContext(lines);
  152. Assert.assertTrue(message + ": unexpected " + l2, l2 == null);
  153. } catch (IOException ioe) {
  154. Assert.assertTrue(message + ": caught " + ioe.getMessage(), false);
  155. }
  156. }
  157. private static void showContext(List lines) {
  158. int n = lines.size();
  159. for (int i = Math.max(0, n - 8); i < n; i++) {
  160. System.err.println(lines.get(i));
  161. }
  162. }
  163. private static String readNonBlankLine(BufferedReader r) throws IOException {
  164. String l = r.readLine();
  165. if (l == null) return null;
  166. l = l.trim();
  167. // comment to include comments when reading
  168. int commentLoc = l.indexOf("//");
  169. if (-1 != commentLoc) {
  170. l = l.substring(0, commentLoc).trim();
  171. }
  172. if ("".equals(l)) return readNonBlankLine(r);
  173. return l;
  174. }
  175. /**
  176. * If there is an expected dir, expect each file in its subtree
  177. * to match a corresponding actual file in the base directory.
  178. * This does NOT check that all actual files have corresponding
  179. * expected files.
  180. * This ignores directory paths containing "CVS".
  181. * @param handler the IMessageHandler sink for error messages
  182. * @param expectedBaseDir the File path to the directory
  183. * containing expected files, all of which are compared
  184. * with any corresponding actual files
  185. * @param actualBaseDir the File path to the base directory
  186. * from which to find any actual files corresponding
  187. * to expected files.
  188. * @return true if all files in the expectedBaseDir directory tree
  189. * have matching files in the actualBaseDir directory tree.
  190. */
  191. public static boolean sameDirectoryContents(
  192. final IMessageHandler handler,
  193. final File expectedBaseDir,
  194. final File actualBaseDir,
  195. final boolean fastFail) {
  196. LangUtil.throwIaxIfNull(handler, "handler");
  197. if (!FileUtil.canReadDir(expectedBaseDir)) {
  198. MessageUtil.fail(handler, " expected dir not found: " + expectedBaseDir);
  199. return false;
  200. }
  201. if (!FileUtil.canReadDir(actualBaseDir)) {
  202. MessageUtil.fail(handler, " actual dir not found: " + actualBaseDir);
  203. return false;
  204. }
  205. String[] paths = FileUtil.listFiles(expectedBaseDir);
  206. boolean result = true;
  207. for (int i = 0; i < paths.length; i++) {
  208. if (-1 != paths[i].indexOf("CVS")) {
  209. continue;
  210. }
  211. if (!sameFiles(handler, expectedBaseDir, actualBaseDir, paths[i]) && result) {
  212. result = false;
  213. if (fastFail) {
  214. break;
  215. }
  216. }
  217. }
  218. return result;
  219. }
  220. //------------ File-comparison utilities (XXX need their own class...)
  221. /**
  222. * Test interface to
  223. * compare two files, line by line, and report differences as one FAIL message
  224. * if a handler is supplied. This preprocesses .class files by disassembling.
  225. * @param handler the IMessageHandler for any FAIL messages (null to ignore)
  226. * @param expectedFile the File path to the canonical file
  227. * @param actualFile the File path to the actual file, if any
  228. * @return true if the input files are the same, based on per-line comparisons
  229. */
  230. public static boolean sameFiles (
  231. IMessageHandler handler,
  232. File expectedFile,
  233. File actualFile) {
  234. return doSameFile(handler, null, null, expectedFile, actualFile);
  235. }
  236. /**
  237. * Test interface to
  238. * compare two files, line by line, and report differences as one FAIL message
  239. * if a handler is supplied. This preprocesses .class files by disassembling.
  240. * This method assumes that the files are at the same offset from two
  241. * respective base directories.
  242. * @param handler the IMessageHandler for any FAIL messages (null to ignore)
  243. * @param expectedBaseDir the File path to the canonical file base directory
  244. * @param actualBaseDir the File path to the actual file base directory
  245. * @param path the String path offset from the base directories
  246. * @return true if the input files are the same, based on per-line comparisons
  247. */
  248. public static boolean sameFiles (
  249. IMessageHandler handler,
  250. File expectedBaseDir,
  251. File actualBaseDir,
  252. String path) {
  253. File actualFile = new File(actualBaseDir, path);
  254. File expectedFile = new File(expectedBaseDir, path);
  255. return doSameFile(handler, expectedBaseDir, actualBaseDir, expectedFile, actualFile);
  256. }
  257. /**
  258. * This does the work, selecting a lineator subclass and converting public
  259. * API's to JDiff APIs for comparison.
  260. * Currently, all jdiff interfaces are method-local, so this class will load
  261. * without it; if we do use it, we can avoid the duplication.
  262. */
  263. private static boolean doSameFile(
  264. IMessageHandler handler,
  265. File expectedBaseDir,
  266. File actualBaseDir,
  267. File expectedFile,
  268. File actualFile) {
  269. String path = expectedFile.getPath();
  270. // XXX permit user to specify lineator
  271. ILineator lineator = Lineator.TEXT;
  272. if (path.endsWith(".class")) {
  273. if (ClassLineator.haveDisassembler() ) {
  274. lineator = Lineator.CLASS;
  275. } else {
  276. MessageUtil.abort(handler, "skipping - dissassembler not available");
  277. return false;
  278. }
  279. }
  280. CanonicalLine[] actualLines = null;
  281. CanonicalLine[] expectedLines = null;
  282. try {
  283. actualLines = lineator.getLines(handler, actualFile, actualBaseDir);
  284. expectedLines = lineator.getLines(handler, expectedFile, expectedBaseDir);
  285. } catch (IOException e) {
  286. MessageUtil.fail(handler, "rendering lines ", e);
  287. return false;
  288. }
  289. if (!LangUtil.isEmpty(actualLines) && !LangUtil.isEmpty(expectedLines)) {
  290. // here's the transmutation back to jdiff - extract if publishing JDiff
  291. CanonicalLine[][] clines = new CanonicalLine[][] { expectedLines, actualLines };
  292. FileLine[][] flines = new FileLine[2][];
  293. for (int i = 0; i < clines.length; i++) {
  294. CanonicalLine[] cline = clines[i];
  295. FileLine[] fline = new FileLine[cline.length];
  296. for (int j = 0; j < fline.length; j++) {
  297. fline[j] = new FileLine(cline[j].canonical, cline[j].line);
  298. }
  299. flines[i] = fline;
  300. }
  301. Diff.change edits = new Diff(flines[0], flines[1]).diff_2(false);
  302. if ((null == edits) || (0 == (edits.inserted + edits.deleted))) {
  303. // XXX confirm with jdiff that null means no edits
  304. return true;
  305. } else {
  306. //String m = render(handler, edits, flines[0], flines[1]);
  307. StringWriter writer = new StringWriter();
  308. DiffNormalOutput out = new DiffNormalOutput(flines[0], flines[1]);
  309. out.setOut(writer);
  310. out.setLineSeparator(LangUtil.EOL);
  311. try {
  312. out.writeScript(edits);
  313. } catch (IOException e) {
  314. MessageUtil.fail(handler, "rendering edits", e);
  315. } finally {
  316. if (null != writer) {
  317. try { writer.close(); }
  318. catch (IOException e) {
  319. MessageUtil.fail(handler, "closing after rendering edits", e);
  320. }
  321. }
  322. }
  323. String message = "diff between "
  324. + path
  325. + " in expected dir "
  326. + expectedBaseDir
  327. + " and actual dir "
  328. + actualBaseDir
  329. + LangUtil.EOL
  330. + writer.toString();
  331. MessageUtil.fail(handler, message);
  332. }
  333. }
  334. return false;
  335. }
  336. /** component that reduces file to CanonicalLine[] */
  337. public static interface ILineator {
  338. /** Lineator suitable for text files */
  339. public static final ILineator TEXT = new TextLineator();
  340. /** Lineator suitable for class files (disassembles first) */
  341. public static final ILineator CLASS = new ClassLineator();
  342. /**
  343. * Reduce file to CanonicalLine[].
  344. * @param handler the IMessageHandler for errors (may be null)
  345. * @param file the File to render
  346. * @param basedir the File for the base directory (may be null)
  347. * @return CanonicalLine[] of lines - not null, but perhaps empty
  348. */
  349. CanonicalLine[] getLines(
  350. IMessageHandler handler,
  351. File file,
  352. File basedir) throws IOException;
  353. }
  354. /** alias for jdiff FileLine to avoid client binding */
  355. public static class CanonicalLine {
  356. public static final CanonicalLine[] NO_LINES = new CanonicalLine[0];
  357. /** canonical variant of line for comparison */
  358. public final String canonical;
  359. /** actual line, for logging */
  360. public final String line;
  361. public CanonicalLine(String canonical, String line) {
  362. this.canonical = canonical;
  363. this.line = line;
  364. }
  365. public String toString() {
  366. return line;
  367. }
  368. }
  369. private abstract static class Lineator implements ILineator {
  370. /**
  371. * Reduce file to CanonicalLine[].
  372. * @param handler the IMessageHandler for errors (may be null)
  373. * @param file the File to render
  374. * @param basedir the File for the base directory (may be null)
  375. */
  376. public CanonicalLine[] getLines(
  377. IMessageHandler handler,
  378. File file,
  379. File basedir)
  380. throws IOException {
  381. if (!file.canRead() || !file.isFile()) {
  382. MessageUtil.error(handler, "not readable file: " + basedir + " - " + file);
  383. return null;
  384. }
  385. // capture file as FileLine[]
  386. InputStream in = null;
  387. /*String path = */FileUtil.normalizedPath(file, basedir);
  388. LineStream capture = new LineStream();
  389. try {
  390. lineate(capture, handler, basedir, file);
  391. } catch (IOException e) {
  392. MessageUtil.fail(handler,
  393. "NormalizedCompareFiles IOException reading " + file, e);
  394. return null;
  395. } finally {
  396. if (null != in) {
  397. try { in.close(); }
  398. catch (IOException e) {} // ignore
  399. }
  400. capture.flush();
  401. capture.close();
  402. }
  403. String missed = capture.getMissed();
  404. if (!LangUtil.isEmpty(missed)) {
  405. MessageUtil.warn(handler,
  406. "NormalizedCompareFiles missed input: "
  407. + missed);
  408. return null;
  409. } else {
  410. String[] lines = capture.getLines();
  411. CanonicalLine[] result = new CanonicalLine[lines.length];
  412. for (int i = 0; i < lines.length; i++) {
  413. result[i] = new CanonicalLine(lines[i], lines[i]);
  414. }
  415. return result;
  416. }
  417. }
  418. protected abstract void lineate(
  419. PrintStream sink,
  420. IMessageHandler handler,
  421. File basedir,
  422. File file) throws IOException;
  423. }
  424. private static class TextLineator extends Lineator {
  425. protected void lineate(
  426. PrintStream sink,
  427. IMessageHandler handler,
  428. File basedir,
  429. File file) throws IOException {
  430. InputStream in = null;
  431. try {
  432. in = new FileInputStream(file);
  433. FileUtil.copyStream(new DataInputStream(in), sink);
  434. } finally {
  435. try { in.close(); }
  436. catch (IOException e) {} // ignore
  437. }
  438. }
  439. }
  440. public static class ClassLineator extends Lineator {
  441. protected void lineate(
  442. PrintStream sink,
  443. IMessageHandler handler,
  444. File basedir,
  445. File file) throws IOException {
  446. String name = FileUtil.fileToClassName(basedir, file);
  447. // XXX re-enable preflight?
  448. // if ((null != basedir) && (path.length()-6 != name.length())) {
  449. // MessageUtil.error(handler, "unexpected class name \""
  450. // + name + "\" for path " + path);
  451. // return null;
  452. // }
  453. disassemble(handler, basedir, name, sink);
  454. }
  455. public static boolean haveDisassembler() {
  456. try {
  457. return (null != Class.forName("org.aspectj.weaver.bcel.LazyClassGen"));
  458. } catch (ClassNotFoundException e) {
  459. // XXX fix
  460. //System.err.println(e.getMessage());
  461. //e.printStackTrace(System.err);
  462. return false;
  463. }
  464. }
  465. /** XXX dependency on bcweaver/bcel */
  466. private static void disassemble(
  467. IMessageHandler handler,
  468. File basedir,
  469. String name,
  470. PrintStream out) throws IOException {
  471. // LazyClassGen.disassemble(FileUtil.normalizedPath(basedir), name, capture);
  472. Throwable thrown = null;
  473. String basedirPath = FileUtil.normalizedPath(basedir);
  474. // XXX use reflection utilities to invoke dissassembler?
  475. try {
  476. // XXX need test to detect when this is refactored
  477. Class c = Class.forName("org.aspectj.weaver.bcel.LazyClassGen");
  478. Method m = c.getMethod("disassemble",
  479. new Class[] {String.class, String.class, PrintStream.class});
  480. m.invoke(null, new Object[] { basedirPath, name, out});
  481. } catch (ClassNotFoundException e) {
  482. thrown = e;
  483. } catch (NoSuchMethodException e) {
  484. thrown = e;
  485. } catch (IllegalAccessException e) {
  486. thrown = e;
  487. } catch (InvocationTargetException e) {
  488. Throwable t = e.getTargetException();
  489. if (t instanceof IOException) {
  490. throw (IOException) t;
  491. }
  492. thrown = t;
  493. }
  494. if (null != thrown) {
  495. MessageUtil.fail(handler, "disassembling " + name + " path: " + basedirPath,
  496. thrown);
  497. }
  498. }
  499. }
  500. /**
  501. * Capture PrintStream output to String[]
  502. * (delimiting component String on println()),
  503. * also showing any missed text.
  504. */
  505. public static class LineStream extends PrintStream {
  506. StringBuffer sb = new StringBuffer();
  507. ByteArrayOutputStream missed;
  508. ArrayList sink;
  509. public LineStream() {
  510. super(new ByteArrayOutputStream());
  511. this.sink = new ArrayList();
  512. missed = (ByteArrayOutputStream) out;
  513. }
  514. /** @return any text not captured by our overrides */
  515. public String getMissed() {
  516. return missed.toString();
  517. }
  518. /** clear captured lines (but not missed text) */
  519. public void clear() {
  520. sink.clear();
  521. }
  522. /**
  523. * Get String[] of lines printed,
  524. * delimited by println(..) calls.
  525. * @return lines printed, exclusive of any not yet terminated by newline
  526. */
  527. public String[] getLines() {
  528. return (String[]) sink.toArray(new String[0]);
  529. }
  530. // ---------- PrintStream overrides
  531. public void println(Object x) {
  532. println(x.toString());
  533. }
  534. public void print(Object obj) {
  535. print(obj.toString());
  536. }
  537. public void println(char c) {
  538. sb.append(c);
  539. println();
  540. }
  541. public void println(char[] c) {
  542. sb.append(c);
  543. println();
  544. }
  545. public void print(char c) {
  546. sb.append(c);
  547. }
  548. public void print(char[] c) {
  549. sb.append(c);
  550. }
  551. public void println(String s) {
  552. print(s);
  553. println();
  554. }
  555. public void print(String s) {
  556. sb.append(s);
  557. }
  558. public void println() {
  559. String line = sb.toString();
  560. sink.add(line);
  561. sb.setLength(0);
  562. }
  563. }
  564. }