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 29KB

21 years ago
21 years ago
21 years ago
21 years ago
19 years ago
21 years ago
19 years ago
19 years ago
21 years ago
19 years ago
21 years ago
19 years ago
21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  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 Eclipse Public License v1.0
  6. * which accompanies this distribution and is available at
  7. * http://www.eclipse.org/legal/epl-v10.html
  8. *
  9. * Contributors:
  10. * Xerox/PARC initial implementation
  11. * ******************************************************************/
  12. package org.aspectj.testing.util;
  13. import java.io.BufferedReader;
  14. import java.io.ByteArrayOutputStream;
  15. import java.io.DataInputStream;
  16. import java.io.File;
  17. import java.io.FileInputStream;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.PrintStream;
  21. import java.io.PrintWriter;
  22. import java.io.StringReader;
  23. import java.io.StringWriter;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;
  26. import java.lang.reflect.Modifier;
  27. import java.net.MalformedURLException;
  28. import java.net.URL;
  29. import java.util.ArrayList;
  30. import java.util.Arrays;
  31. import java.util.Collection;
  32. import java.util.Collections;
  33. import java.util.Enumeration;
  34. import java.util.HashMap;
  35. import java.util.HashSet;
  36. import java.util.Iterator;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Properties;
  40. import java.util.Set;
  41. import jdiff.text.FileLine;
  42. import jdiff.util.Diff;
  43. import jdiff.util.DiffNormalOutput;
  44. import junit.framework.Assert;
  45. import junit.framework.Test;
  46. import junit.framework.TestCase;
  47. import junit.framework.TestResult;
  48. import junit.framework.TestSuite;
  49. import junit.runner.TestCaseClassLoader;
  50. import org.aspectj.bridge.IMessageHandler;
  51. import org.aspectj.bridge.MessageUtil;
  52. import org.aspectj.util.FileUtil;
  53. import org.aspectj.util.LangUtil;
  54. import org.aspectj.util.Reflection;
  55. /**
  56. * Things that junit should perhaps have, but doesn't. Note the file-comparison methods require JDiff to run, but JDiff types are
  57. * not required to resolve this class. Also, the bytecode weaver is required to compare class files, but not to compare other files.
  58. */
  59. public final class TestUtil {
  60. private static final boolean JAVA_5_VM;
  61. private static final String ASPECTJRT_KEY = "aspectjrt";
  62. private static final String TESTING_CLIENT_KEY = "testing-client";
  63. public static final URL BAD_URL;
  64. private static final File LIB_DIR;
  65. private static final Properties LIB_RPATHS;
  66. private static final Map LIB_ENTRIES;
  67. private static File ASPECTJRT_PATH;
  68. static {
  69. {
  70. String[] paths = { "sp:aspectjrt.path", "sp:aspectjrt.jar", "../lib/test/aspectjrt.jar",
  71. "../aj-build/jars/aspectj5rt-all.jar", "../aj-build/jars/runtime.jar", "../runtime/bin" };
  72. ASPECTJRT_PATH = FileUtil.getBestFile(paths);
  73. }
  74. {
  75. boolean j5 = false;
  76. try {
  77. Class.forName("java.lang.annotation.Annotation");
  78. j5 = true;
  79. } catch (Throwable t) {
  80. }
  81. JAVA_5_VM = j5;
  82. }
  83. {
  84. URL url = null;
  85. try {
  86. url = new URL("http://eclipse.org/BADURL");
  87. } catch (MalformedURLException e) {
  88. // ignore - hopefully never
  89. }
  90. BAD_URL = url;
  91. }
  92. {
  93. File file = new File("lib");
  94. if (!isLibDir(file)) {
  95. File cur = new File(".").getAbsoluteFile();
  96. File parent = cur.getParentFile();
  97. while (null != parent) {
  98. file = new File(parent, "lib");
  99. if (isLibDir(file)) {
  100. break;
  101. }
  102. parent = parent.getParentFile();
  103. }
  104. if (null == parent) {
  105. file = new File("NOT IN ASPECTJ TREE");
  106. }
  107. }
  108. LIB_DIR = file;
  109. }
  110. LIB_RPATHS = new Properties();
  111. LIB_RPATHS.setProperty(ASPECTJRT_KEY, "tests/aspectjrt.jar");
  112. LIB_RPATHS.setProperty(TESTING_CLIENT_KEY, "tests/testing-client.jar");
  113. // TODO support others loaded dynamically
  114. Map map = new HashMap();
  115. for (Iterator iter = LIB_RPATHS.keySet().iterator(); iter.hasNext();) {
  116. String key = (String) iter.next();
  117. String path = LIB_RPATHS.getProperty(key);
  118. File file = null;
  119. URL url = null;
  120. try {
  121. file = libFile(path);
  122. url = libURL(path);
  123. } catch (IllegalArgumentException e) {
  124. file = new File(path + " not found");
  125. url = BAD_URL;
  126. } finally {
  127. map.put(key + ".file", file);
  128. map.put(key + ".url", url);
  129. }
  130. }
  131. // TODO support changing entries, etc.
  132. LIB_ENTRIES = Collections.unmodifiableMap(map);
  133. }
  134. private static boolean isLibDir(File lib) {
  135. return new File(lib, "test" + File.separator + "aspectjrt.jar").exists();
  136. }
  137. private TestUtil() {
  138. super();
  139. }
  140. public static boolean is15VMOrGreater() {
  141. return JAVA_5_VM;
  142. }
  143. public static File aspectjrtPath() {
  144. return ASPECTJRT_PATH;
  145. }
  146. public static URL fileToURL(File file) {
  147. try {
  148. return file.toURL();
  149. } catch (MalformedURLException e) {
  150. return null;
  151. }
  152. }
  153. public static String filesToPath(File[] entries) {
  154. return toPath(entries);
  155. }
  156. public static String urlsToPath(URL[] entries) {
  157. return toPath(entries);
  158. }
  159. /**
  160. * untyped interface for mixed entries
  161. */
  162. public static String filesOrurlsToPath(Object[] entries) {
  163. return toPath(entries);
  164. }
  165. /**
  166. * This relies on these being File (where toString() == getPath()) or URL (where toString() == toExternalForm()).
  167. *
  168. * @param entries the Object[] of File or URL elements
  169. * @return the String with entries dlimited by the File.pathSeparator
  170. */
  171. private static String toPath(Object[] entries) {
  172. if ((null == entries) || (0 == entries.length)) {
  173. return "";
  174. }
  175. StringBuffer path = new StringBuffer();
  176. boolean started = false;
  177. for (int i = 0; i < entries.length; i++) {
  178. if (null != entries[i]) {
  179. if (started) {
  180. path.append(File.pathSeparator);
  181. } else {
  182. started = true;
  183. }
  184. path.append(entries[i].toString());
  185. }
  186. }
  187. return path.toString();
  188. }
  189. /**
  190. * @param input the String to parse for [on|off|true|false]
  191. * @throws IllegalArgumentException if input is bad
  192. **/
  193. public static boolean parseBoolean(String input) {
  194. return parseBoolean(input, true);
  195. }
  196. /**
  197. * @param input the String to parse for [on|off|true|false]
  198. * @param iaxOnError if true and input is bad, throw IllegalArgumentException
  199. * @return true if input is true, false otherwise
  200. * @throws IllegalArgumentException if iaxOnError and input is bad
  201. */
  202. public static boolean parseBoolean(final String input, boolean iaxOnError) {
  203. final String syntax = ": [on|off|true|false]";
  204. if (null == input) {
  205. return false;
  206. }
  207. String lc = input.trim().toLowerCase();
  208. boolean result = false;
  209. boolean valid = false;
  210. switch (lc.length()) {
  211. case 2:
  212. if (valid = "on".equals(lc)) {
  213. result = true;
  214. }
  215. break;
  216. case 3:
  217. valid = "off".equals(lc);
  218. break;
  219. case 4:
  220. if (valid = "true".equals(lc)) {
  221. result = true;
  222. }
  223. break;
  224. case 5:
  225. valid = "false".equals(lc);
  226. break;
  227. }
  228. if (iaxOnError && !valid) {
  229. throw new IllegalArgumentException(input + syntax);
  230. }
  231. return result;
  232. }
  233. public static File aspectjrtJarFile() {
  234. return (File) LIB_ENTRIES.get(ASPECTJRT_KEY + ".file");
  235. }
  236. public static URL aspectjrtJarURL() {
  237. return (URL) LIB_ENTRIES.get(ASPECTJRT_KEY + ".url");
  238. }
  239. public static File testingClientJarFile() {
  240. return (File) LIB_ENTRIES.get(TESTING_CLIENT_KEY + ".file");
  241. }
  242. public static URL testingClientJarURL() {
  243. return (URL) LIB_ENTRIES.get(TESTING_CLIENT_KEY + ".url");
  244. }
  245. /**
  246. *
  247. * @param rpath the String relative path from the library directory to a resource that must exist (may be a directory), using
  248. * forward slashes as a file separator
  249. * @return the File path
  250. * @throws IllegalArgumentException if no such directory or file
  251. */
  252. public static File libFile(String rpath) {
  253. if ((null == rpath) || (0 == rpath.length())) {
  254. throw new IllegalArgumentException("no input");
  255. }
  256. rpath = rpath.replace('/', File.separatorChar);
  257. File result = new File(LIB_DIR, rpath);
  258. if (result.exists()) {
  259. return result;
  260. }
  261. throw new IllegalArgumentException("not in " + LIB_DIR + ": " + rpath);
  262. }
  263. /**
  264. * Like libPath, only it returns a URL.
  265. *
  266. * @return URL or null if it does not exist
  267. * @throws IllegalArgumentException if no such directory or file
  268. */
  269. public static URL libURL(String rpath) {
  270. File file = libFile(rpath);
  271. try {
  272. return file.toURL();
  273. } catch (MalformedURLException e) {
  274. throw new IllegalArgumentException("bad URL from: " + file);
  275. }
  276. }
  277. // ---- arrays
  278. public static void assertArrayEquals(String msg, Object[] expected, Object[] found) {
  279. TestCase.assertEquals(msg, Arrays.asList(expected), Arrays.asList(found));
  280. }
  281. // ---- unordered
  282. public static void assertSetEquals(Collection expected, Collection found) {
  283. assertSetEquals(null, expected, found);
  284. }
  285. public static void assertSetEquals(String msg, Object[] expected, Object[] found) {
  286. assertSetEquals(msg, Arrays.asList(expected), Arrays.asList(found));
  287. }
  288. public static void assertSetEquals(String msg, Collection expected, Collection found) {
  289. msg = (msg == null) ? "" : msg + ": ";
  290. Set results1 = new HashSet(found);
  291. results1.removeAll(expected);
  292. Set results2 = new HashSet(expected);
  293. results2.removeAll(found);
  294. if (results1.isEmpty()) {
  295. TestCase.assertTrue(msg + "Expected but didn't find: " + results2.toString(), results2.isEmpty());
  296. } else if (results2.isEmpty()) {
  297. TestCase.assertTrue(msg + "Didn't expect: " + results1.toString(), results1.isEmpty());
  298. } else {
  299. TestCase.assertTrue(msg + "Expected but didn't find: " + results2.toString() + "\nDidn't expect: "
  300. + results1.toString(), false);
  301. }
  302. }
  303. // ---- objects
  304. public static void assertCommutativeEquals(Object a, Object b, boolean should) {
  305. TestCase.assertEquals(a + " equals " + b, should, a.equals(b));
  306. TestCase.assertEquals(b + " equals " + a, should, b.equals(a));
  307. assertHashEquals(a, b, should);
  308. }
  309. private static void assertHashEquals(Object s, Object t, boolean should) {
  310. if (should) {
  311. TestCase.assertTrue(s + " does not hash to same as " + t, s.hashCode() == t.hashCode());
  312. } else {
  313. if (s.hashCode() == t.hashCode()) {
  314. System.err.println("warning: hash collision with hash = " + t.hashCode());
  315. System.err.println(" for " + s);
  316. System.err.println(" and " + t);
  317. }
  318. }
  319. }
  320. // -- reflective stuff
  321. public static void runMain(String classPath, String className) {
  322. runMethod(classPath, className, "main", new Object[] { new String[0] });
  323. }
  324. public static Object runMethod(String classPath, String className, String methodName, Object[] args) {
  325. classPath += File.pathSeparator + System.getProperty("java.class.path");
  326. ClassLoader loader = new TestCaseClassLoader(classPath);
  327. Class c = null;
  328. try {
  329. c = loader.loadClass(className);
  330. } catch (ClassNotFoundException e) {
  331. Assert.assertTrue("unexpected exception: " + e, false);
  332. }
  333. return Reflection.invokestaticN(c, methodName, args);
  334. }
  335. /**
  336. * Checks that two multi-line strings have the same value. Each line is trimmed before comparision Produces an error on the
  337. * particular line of conflict
  338. */
  339. public static void assertMultiLineStringEquals(String message, String s1, String s2) {
  340. try {
  341. BufferedReader r1 = new BufferedReader(new StringReader(s1));
  342. BufferedReader r2 = new BufferedReader(new StringReader(s2));
  343. List lines = new ArrayList();
  344. String l1, l2;
  345. int index = 1;
  346. while (true) {
  347. l1 = readNonBlankLine(r1);
  348. l2 = readNonBlankLine(r2);
  349. if (l1 == null || l2 == null)
  350. break;
  351. if (l1.equals(l2)) {
  352. lines.add(l1);
  353. } else {
  354. showContext(lines);
  355. Assert.assertEquals(message + "(line " + index + "):\n" + l1 + "\n" + l2, l1, l2);
  356. }
  357. index++;
  358. }
  359. if (l1 != null)
  360. showContext(lines);
  361. Assert.assertTrue(message + ": unexpected " + l1, l1 == null);
  362. if (l2 != null)
  363. showContext(lines);
  364. Assert.assertTrue(message + ": unexpected " + l2, l2 == null);
  365. } catch (IOException ioe) {
  366. Assert.assertTrue(message + ": caught " + ioe.getMessage(), false);
  367. }
  368. }
  369. private static void showContext(List lines) {
  370. int n = lines.size();
  371. for (int i = Math.max(0, n - 8); i < n; i++) {
  372. System.err.println(lines.get(i));
  373. }
  374. }
  375. private static String readNonBlankLine(BufferedReader r) throws IOException {
  376. String l = r.readLine();
  377. if (l == null)
  378. return null;
  379. l = l.trim();
  380. // comment to include comments when reading
  381. int commentLoc = l.indexOf("//");
  382. if (-1 != commentLoc) {
  383. l = l.substring(0, commentLoc).trim();
  384. }
  385. if ("".equals(l))
  386. return readNonBlankLine(r);
  387. return l;
  388. }
  389. /**
  390. * If there is an expected dir, expect each file in its subtree to match a corresponding actual file in the base directory. This
  391. * does NOT check that all actual files have corresponding expected files. This ignores directory paths containing "CVS".
  392. *
  393. * @param handler the IMessageHandler sink for error messages
  394. * @param expectedBaseDir the File path to the directory containing expected files, all of which are compared with any
  395. * corresponding actual files
  396. * @param actualBaseDir the File path to the base directory from which to find any actual files corresponding to expected files.
  397. * @return true if all files in the expectedBaseDir directory tree have matching files in the actualBaseDir directory tree.
  398. */
  399. public static boolean sameDirectoryContents(final IMessageHandler handler, final File expectedBaseDir,
  400. final File actualBaseDir, final boolean fastFail) {
  401. LangUtil.throwIaxIfNull(handler, "handler");
  402. if (!FileUtil.canReadDir(expectedBaseDir)) {
  403. MessageUtil.fail(handler, " expected dir not found: " + expectedBaseDir);
  404. return false;
  405. }
  406. if (!FileUtil.canReadDir(actualBaseDir)) {
  407. MessageUtil.fail(handler, " actual dir not found: " + actualBaseDir);
  408. return false;
  409. }
  410. String[] paths = FileUtil.listFiles(expectedBaseDir);
  411. boolean result = true;
  412. for (int i = 0; i < paths.length; i++) {
  413. if (-1 != paths[i].indexOf("CVS")) {
  414. continue;
  415. }
  416. if (!sameFiles(handler, expectedBaseDir, actualBaseDir, paths[i]) && result) {
  417. result = false;
  418. if (fastFail) {
  419. break;
  420. }
  421. }
  422. }
  423. return result;
  424. }
  425. // ------------ File-comparison utilities (XXX need their own class...)
  426. /**
  427. * Test interface to compare two files, line by line, and report differences as one FAIL message if a handler is supplied. This
  428. * preprocesses .class files by disassembling.
  429. *
  430. * @param handler the IMessageHandler for any FAIL messages (null to ignore)
  431. * @param expectedFile the File path to the canonical file
  432. * @param actualFile the File path to the actual file, if any
  433. * @return true if the input files are the same, based on per-line comparisons
  434. */
  435. public static boolean sameFiles(IMessageHandler handler, File expectedFile, File actualFile) {
  436. return doSameFile(handler, null, null, expectedFile, actualFile);
  437. }
  438. /**
  439. * Test interface to compare two files, line by line, and report differences as one FAIL message if a handler is supplied. This
  440. * preprocesses .class files by disassembling. This method assumes that the files are at the same offset from two respective
  441. * base directories.
  442. *
  443. * @param handler the IMessageHandler for any FAIL messages (null to ignore)
  444. * @param expectedBaseDir the File path to the canonical file base directory
  445. * @param actualBaseDir the File path to the actual file base directory
  446. * @param path the String path offset from the base directories
  447. * @return true if the input files are the same, based on per-line comparisons
  448. */
  449. public static boolean sameFiles(IMessageHandler handler, File expectedBaseDir, File actualBaseDir, String path) {
  450. File actualFile = new File(actualBaseDir, path);
  451. File expectedFile = new File(expectedBaseDir, path);
  452. return doSameFile(handler, expectedBaseDir, actualBaseDir, expectedFile, actualFile);
  453. }
  454. /**
  455. * This does the work, selecting a lineator subclass and converting public API's to JDiff APIs for comparison. Currently, all
  456. * jdiff interfaces are method-local, so this class will load without it; if we do use it, we can avoid the duplication.
  457. */
  458. private static boolean doSameFile(IMessageHandler handler, File expectedBaseDir, File actualBaseDir, File expectedFile,
  459. File actualFile) {
  460. String path = expectedFile.getPath();
  461. // XXX permit user to specify lineator
  462. ILineator lineator = Lineator.TEXT;
  463. if (path.endsWith(".class")) {
  464. if (ClassLineator.haveDisassembler()) {
  465. lineator = Lineator.CLASS;
  466. } else {
  467. MessageUtil.abort(handler, "skipping - dissassembler not available");
  468. return false;
  469. }
  470. }
  471. CanonicalLine[] actualLines = null;
  472. CanonicalLine[] expectedLines = null;
  473. try {
  474. actualLines = lineator.getLines(handler, actualFile, actualBaseDir);
  475. expectedLines = lineator.getLines(handler, expectedFile, expectedBaseDir);
  476. } catch (IOException e) {
  477. MessageUtil.fail(handler, "rendering lines ", e);
  478. return false;
  479. }
  480. if (!LangUtil.isEmpty(actualLines) && !LangUtil.isEmpty(expectedLines)) {
  481. // here's the transmutation back to jdiff - extract if publishing
  482. // JDiff
  483. CanonicalLine[][] clines = new CanonicalLine[][] { expectedLines, actualLines };
  484. FileLine[][] flines = new FileLine[2][];
  485. for (int i = 0; i < clines.length; i++) {
  486. CanonicalLine[] cline = clines[i];
  487. FileLine[] fline = new FileLine[cline.length];
  488. for (int j = 0; j < fline.length; j++) {
  489. fline[j] = new FileLine(cline[j].canonical, cline[j].line);
  490. }
  491. flines[i] = fline;
  492. }
  493. Diff.change edits = new Diff(flines[0], flines[1]).diff_2(false);
  494. if ((null == edits) || (0 == (edits.inserted + edits.deleted))) {
  495. // XXX confirm with jdiff that null means no edits
  496. return true;
  497. } else {
  498. // String m = render(handler, edits, flines[0], flines[1]);
  499. StringWriter writer = new StringWriter();
  500. DiffNormalOutput out = new DiffNormalOutput(flines[0], flines[1]);
  501. out.setOut(writer);
  502. out.setLineSeparator(LangUtil.EOL);
  503. try {
  504. out.writeScript(edits);
  505. } catch (IOException e) {
  506. MessageUtil.fail(handler, "rendering edits", e);
  507. } finally {
  508. if (null != writer) {
  509. try {
  510. writer.close();
  511. } catch (IOException e) {
  512. MessageUtil.fail(handler, "closing after rendering edits", e);
  513. }
  514. }
  515. }
  516. String message = "diff between " + path + " in expected dir " + expectedBaseDir + " and actual dir "
  517. + actualBaseDir + LangUtil.EOL + writer.toString();
  518. MessageUtil.fail(handler, message);
  519. }
  520. }
  521. return false;
  522. }
  523. public static String cleanTestName(String name) {
  524. name = name.replace(' ', '_');
  525. return name;
  526. }
  527. public static Test skipTest(String tests) {
  528. // could printStackTrace to give more context if needed...
  529. System.err.println("skipping tests " + tests);
  530. return testNamed("skipping tests " + tests);
  531. }
  532. public static Test testNamed(String named) {
  533. final String name = cleanTestName(named);
  534. return new Test() {
  535. public int countTestCases() {
  536. return 1;
  537. }
  538. public void run(TestResult r) {
  539. r.startTest(this);
  540. r.endTest(this);
  541. }
  542. public String toString() {
  543. return name;
  544. }
  545. };
  546. }
  547. /**
  548. * @param sink the TestSuite sink to add result to
  549. * @param sourceName the String fully-qualified name of the class with a suite() method to load
  550. */
  551. public static void loadTestsReflectively(TestSuite sink, String sourceName, boolean ignoreError) {
  552. Throwable thrown = null;
  553. try {
  554. ClassLoader loader = sink.getClass().getClassLoader();
  555. Class sourceClass = loader.loadClass(sourceName);
  556. if (!Modifier.isPublic(sourceClass.getModifiers())) {
  557. errorSuite(sink, sourceName, "not public class");
  558. return;
  559. }
  560. Method suiteMethod = sourceClass.getMethod("suite", new Class[0]);
  561. int mods = suiteMethod.getModifiers();
  562. if (!Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  563. errorSuite(sink, sourceName, "not static method");
  564. return;
  565. }
  566. if (!Modifier.isPublic(mods)) {
  567. errorSuite(sink, sourceName, "not public method");
  568. return;
  569. }
  570. if (!Test.class.isAssignableFrom(suiteMethod.getReturnType())) {
  571. errorSuite(sink, sourceName, "suite() does not return Test");
  572. return;
  573. }
  574. Object result = suiteMethod.invoke(null, new Object[0]);
  575. Test test = (Test) result;
  576. if (!(test instanceof TestSuite)) {
  577. sink.addTest(test);
  578. } else {
  579. TestSuite source = (TestSuite) test;
  580. Enumeration tests = source.tests();
  581. while (tests.hasMoreElements()) {
  582. sink.addTest((Test) tests.nextElement());
  583. }
  584. }
  585. } catch (ClassNotFoundException e) {
  586. thrown = e;
  587. } catch (SecurityException e) {
  588. thrown = e;
  589. } catch (NoSuchMethodException e) {
  590. thrown = e;
  591. } catch (IllegalArgumentException e) {
  592. thrown = e;
  593. } catch (IllegalAccessException e) {
  594. thrown = e;
  595. } catch (InvocationTargetException e) {
  596. thrown = e;
  597. }
  598. if (null != thrown) {
  599. if (ignoreError) {
  600. System.err.println("Error loading " + sourceName);
  601. thrown.printStackTrace(System.err);
  602. } else {
  603. errorSuite(sink, sourceName, thrown);
  604. }
  605. }
  606. }
  607. private static void errorSuite(TestSuite sink, String sourceName, Throwable thrown) {
  608. sink.addTest(new ErrorTest(sourceName, thrown));
  609. }
  610. private static void errorSuite(TestSuite sink, String sourceName, String err) {
  611. String message = "bad " + sourceName + ": " + err;
  612. sink.addTest(new ErrorTest(message));
  613. }
  614. /**
  615. * Junit test failure, e.g., to report suite initialization errors at test time.
  616. */
  617. public static class ErrorTest implements Test {
  618. private final Throwable thrown;
  619. public ErrorTest(Throwable thrown) {
  620. this.thrown = thrown;
  621. }
  622. public ErrorTest(String message) {
  623. this.thrown = new Error(message);
  624. }
  625. public ErrorTest(String message, Throwable thrown) {
  626. this(new TestError(message, thrown));
  627. }
  628. public int countTestCases() {
  629. return 1;
  630. }
  631. public void run(TestResult result) {
  632. result.startTest(this);
  633. result.addError(this, thrown);
  634. }
  635. }
  636. /**
  637. * Nested exception - remove when using 1.4 or later.
  638. */
  639. public static class TestError extends Error {
  640. private Throwable thrown;
  641. public TestError(String message) {
  642. super(message);
  643. }
  644. public TestError(String message, Throwable thrown) {
  645. super(message);
  646. this.thrown = thrown;
  647. }
  648. public Throwable getCause() {
  649. return thrown;
  650. }
  651. public void printStackTrace() {
  652. printStackTrace(System.err);
  653. }
  654. public void printStackTrace(PrintStream ps) {
  655. printStackTrace(new PrintWriter(ps));
  656. }
  657. public void printStackTrace(PrintWriter pw) {
  658. super.printStackTrace(pw);
  659. if (null != thrown) {
  660. pw.print("Caused by: ");
  661. thrown.printStackTrace(pw);
  662. }
  663. }
  664. }
  665. /** component that reduces file to CanonicalLine[] */
  666. public static interface ILineator {
  667. /** Lineator suitable for text files */
  668. public static final ILineator TEXT = new TextLineator();
  669. /** Lineator suitable for class files (disassembles first) */
  670. public static final ILineator CLASS = new ClassLineator();
  671. /**
  672. * Reduce file to CanonicalLine[].
  673. *
  674. * @param handler the IMessageHandler for errors (may be null)
  675. * @param file the File to render
  676. * @param basedir the File for the base directory (may be null)
  677. * @return CanonicalLine[] of lines - not null, but perhaps empty
  678. */
  679. CanonicalLine[] getLines(IMessageHandler handler, File file, File basedir) throws IOException;
  680. }
  681. /** alias for jdiff FileLine to avoid client binding */
  682. public static class CanonicalLine {
  683. public static final CanonicalLine[] NO_LINES = new CanonicalLine[0];
  684. /** canonical variant of line for comparison */
  685. public final String canonical;
  686. /** actual line, for logging */
  687. public final String line;
  688. public CanonicalLine(String canonical, String line) {
  689. this.canonical = canonical;
  690. this.line = line;
  691. }
  692. public String toString() {
  693. return line;
  694. }
  695. }
  696. private abstract static class Lineator implements ILineator {
  697. /**
  698. * Reduce file to CanonicalLine[].
  699. *
  700. * @param handler the IMessageHandler for errors (may be null)
  701. * @param file the File to render
  702. * @param basedir the File for the base directory (may be null)
  703. */
  704. public CanonicalLine[] getLines(IMessageHandler handler, File file, File basedir) throws IOException {
  705. if (!file.canRead() || !file.isFile()) {
  706. MessageUtil.error(handler, "not readable file: " + basedir + " - " + file);
  707. return null;
  708. }
  709. // capture file as FileLine[]
  710. InputStream in = null;
  711. /* String path = */FileUtil.normalizedPath(file, basedir);
  712. LineStream capture = new LineStream();
  713. try {
  714. lineate(capture, handler, basedir, file);
  715. } catch (IOException e) {
  716. MessageUtil.fail(handler, "NormalizedCompareFiles IOException reading " + file, e);
  717. return null;
  718. } finally {
  719. if (null != in) {
  720. try {
  721. in.close();
  722. } catch (IOException e) {
  723. } // ignore
  724. }
  725. capture.flush();
  726. capture.close();
  727. }
  728. String missed = capture.getMissed();
  729. if (!LangUtil.isEmpty(missed)) {
  730. MessageUtil.warn(handler, "NormalizedCompareFiles missed input: " + missed);
  731. return null;
  732. } else {
  733. String[] lines = capture.getLines();
  734. CanonicalLine[] result = new CanonicalLine[lines.length];
  735. for (int i = 0; i < lines.length; i++) {
  736. result[i] = new CanonicalLine(lines[i], lines[i]);
  737. }
  738. return result;
  739. }
  740. }
  741. protected abstract void lineate(PrintStream sink, IMessageHandler handler, File basedir, File file) throws IOException;
  742. }
  743. private static class TextLineator extends Lineator {
  744. protected void lineate(PrintStream sink, IMessageHandler handler, File basedir, File file) throws IOException {
  745. InputStream in = null;
  746. try {
  747. in = new FileInputStream(file);
  748. FileUtil.copyStream(new DataInputStream(in), sink);
  749. } finally {
  750. try {
  751. in.close();
  752. } catch (IOException e) {
  753. } // ignore
  754. }
  755. }
  756. }
  757. public static class ClassLineator extends Lineator {
  758. protected void lineate(PrintStream sink, IMessageHandler handler, File basedir, File file) throws IOException {
  759. String name = FileUtil.fileToClassName(basedir, file);
  760. // XXX re-enable preflight?
  761. // if ((null != basedir) && (path.length()-6 != name.length())) {
  762. // MessageUtil.error(handler, "unexpected class name \""
  763. // + name + "\" for path " + path);
  764. // return null;
  765. // }
  766. disassemble(handler, basedir, name, sink);
  767. }
  768. public static boolean haveDisassembler() {
  769. try {
  770. return (null != Class.forName("org.aspectj.weaver.bcel.LazyClassGen"));
  771. } catch (ClassNotFoundException e) {
  772. // XXX fix
  773. // System.err.println(e.getMessage());
  774. // e.printStackTrace(System.err);
  775. return false;
  776. }
  777. }
  778. /** XXX dependency on bcweaver/bcel */
  779. private static void disassemble(IMessageHandler handler, File basedir, String name, PrintStream out) throws IOException {
  780. // LazyClassGen.disassemble(FileUtil.normalizedPath(basedir), name,
  781. // capture);
  782. Throwable thrown = null;
  783. String basedirPath = FileUtil.normalizedPath(basedir);
  784. // XXX use reflection utilities to invoke dissassembler?
  785. try {
  786. // XXX need test to detect when this is refactored
  787. Class c = Class.forName("org.aspectj.weaver.bcel.LazyClassGen");
  788. Method m = c.getMethod("disassemble", new Class[] { String.class, String.class, PrintStream.class });
  789. m.invoke(null, new Object[] { basedirPath, name, out });
  790. } catch (ClassNotFoundException e) {
  791. thrown = e;
  792. } catch (NoSuchMethodException e) {
  793. thrown = e;
  794. } catch (IllegalAccessException e) {
  795. thrown = e;
  796. } catch (InvocationTargetException e) {
  797. Throwable t = e.getTargetException();
  798. if (t instanceof IOException) {
  799. throw (IOException) t;
  800. }
  801. thrown = t;
  802. }
  803. if (null != thrown) {
  804. MessageUtil.fail(handler, "disassembling " + name + " path: " + basedirPath, thrown);
  805. }
  806. }
  807. }
  808. /**
  809. * Capture PrintStream output to String[] (delimiting component String on println()), also showing any missed text.
  810. */
  811. public static class LineStream extends PrintStream {
  812. StringBuffer sb = new StringBuffer();
  813. ByteArrayOutputStream missed;
  814. ArrayList sink;
  815. public LineStream() {
  816. super(new ByteArrayOutputStream());
  817. this.sink = new ArrayList();
  818. missed = (ByteArrayOutputStream) out;
  819. }
  820. /** @return any text not captured by our overrides */
  821. public String getMissed() {
  822. return missed.toString();
  823. }
  824. /** clear captured lines (but not missed text) */
  825. public void clear() {
  826. sink.clear();
  827. }
  828. /**
  829. * Get String[] of lines printed, delimited by println(..) calls.
  830. *
  831. * @return lines printed, exclusive of any not yet terminated by newline
  832. */
  833. public String[] getLines() {
  834. return (String[]) sink.toArray(new String[0]);
  835. }
  836. // ---------- PrintStream overrides
  837. public void println(Object x) {
  838. println(x.toString());
  839. }
  840. public void print(Object obj) {
  841. print(obj.toString());
  842. }
  843. public void println(char c) {
  844. sb.append(c);
  845. println();
  846. }
  847. public void println(char[] c) {
  848. sb.append(c);
  849. println();
  850. }
  851. public void print(char c) {
  852. sb.append(c);
  853. }
  854. public void print(char[] c) {
  855. sb.append(c);
  856. }
  857. public void println(String s) {
  858. print(s);
  859. println();
  860. }
  861. public void print(String s) {
  862. sb.append(s);
  863. }
  864. public void println() {
  865. String line = sb.toString();
  866. sink.add(line);
  867. sb.setLength(0);
  868. }
  869. }
  870. }