| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.junit; |
| |
| import java.awt.EventQueue; |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FilterOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.lang.management.LockInfo; |
| import java.lang.management.ManagementFactory; |
| import java.lang.management.MonitorInfo; |
| import java.lang.management.PlatformLoggingMXBean; |
| import java.lang.management.RuntimeMXBean; |
| import java.lang.management.ThreadInfo; |
| import java.lang.management.ThreadMXBean; |
| import java.lang.ref.Reference; |
| import java.lang.reflect.Field; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.logging.Level; |
| import java.util.prefs.BackingStoreException; |
| import java.util.prefs.Preferences; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import junit.framework.AssertionFailedError; |
| import junit.framework.TestCase; |
| import junit.framework.TestResult; |
| import org.netbeans.insane.live.LiveReferences; |
| import org.netbeans.insane.live.Path; |
| import org.netbeans.insane.scanner.CountingVisitor; |
| import org.netbeans.insane.scanner.ScannerUtils; |
| import org.netbeans.junit.diff.Diff; |
| import org.netbeans.junit.internal.MemoryPreferencesFactory; |
| import org.netbeans.junit.internal.NbModuleLogHandler; |
| /** |
| * NetBeans extension to JUnit's {@link TestCase}. |
| * Adds various abilities such as comparing golden files, getting a working |
| * directory for test files, testing memory usage, etc. |
| */ |
| public abstract class NbTestCase extends TestCase implements NbTest { |
| static { |
| MethodOrder.initialize(); |
| } |
| /** |
| * active filter |
| */ |
| private Filter filter; |
| /** the amount of time the test was executing for |
| */ |
| private long time; |
| /** our working directory */ |
| private String workDirPath; |
| |
| |
| /** |
| * Constructs a test case with the given name. |
| * Normally you will just use: |
| * <pre> |
| * public class MyTest extends NbTestCase { |
| * public MyTest(String name) {super(name);} |
| * public void testWhatever() {...} |
| * } |
| * </pre> |
| * @param name name of the test case |
| */ |
| public NbTestCase(String name) { |
| super(name); |
| } |
| |
| |
| /** |
| * Sets active filter. |
| * @param filter Filter to be set as active for current test, null will reset filtering. |
| */ |
| public @Override void setFilter(Filter filter) { |
| this.filter = filter; |
| } |
| |
| /** |
| * Returns expected fail message. |
| * @return expected fail message if it's expected this test fail, null otherwise. |
| */ |
| public @Override String getExpectedFail() { |
| if (filter == null) { |
| return null; |
| } |
| return filter.getExpectedFail(this.getName()); |
| } |
| |
| /** |
| * Checks if a test isn't filtered out by the active filter. |
| * @return true if the test can run |
| */ |
| public @Override boolean canRun() { |
| if (NbTestSuite.ignoreRandomFailures()) { |
| if (getClass().isAnnotationPresent(RandomlyFails.class)) { |
| System.err.println("Skipping " + getClass().getName()); |
| return false; |
| } |
| try { |
| if (getClass().getMethod(getName()).isAnnotationPresent(RandomlyFails.class)) { |
| System.err.println("Skipping " + getClass().getName() + "." + getName()); |
| return false; |
| } |
| } catch (NoSuchMethodException x) { |
| // Specially named methods; let it pass. |
| } |
| } |
| if (null == filter) { |
| //System.out.println("NBTestCase.canRun(): filter == null name=" + name ()); |
| return true; // no filter was aplied |
| } |
| boolean isIncluded = filter.isIncluded(this.getName()); |
| //System.out.println("NbTestCase.canRun(): filter.isIncluded(this.getName())="+isIncluded+" ; this="+this); |
| return isIncluded; |
| } |
| |
| /** |
| * Provide ability for tests, setUp and tearDown to request that they run only in the AWT event queue. |
| * By default, false. |
| * @return true to run all test methods, setUp and tearDown in the EQ, false to run in whatever thread |
| */ |
| protected boolean runInEQ() { |
| return false; |
| } |
| |
| private static final long vmDeadline; |
| static { |
| boolean debugMode = false; |
| |
| // check if we are debugged |
| RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); |
| List<String> args = runtime.getInputArguments(); |
| if (args.contains("-Xdebug") || args.contains("-agentlib:jdwp")) { //NOI18N |
| debugMode = true; |
| } else { |
| for (String arg : args) { |
| if (arg.startsWith("-agentlib:jdwp=")) { |
| debugMode = true; |
| break; |
| } |
| } |
| } |
| Integer vmTimeRemaining = Integer.getInteger("nbjunit.hard.timeout"); |
| if (vmTimeRemaining != null && !debugMode) { |
| vmDeadline = System.currentTimeMillis() + vmTimeRemaining; |
| } else { |
| vmDeadline = -1L; |
| } |
| } |
| |
| private static ThreadLocal<Boolean> DEFAULT_TIME_OUT_CALLED = new ThreadLocal<Boolean>(); |
| /** Provides support for tests that can have problems with terminating. |
| * Runs the test in a "watchdog" that measures the time the test shall |
| * take and if it does not terminate it reports a failure including a thread dump. |
| * <p>If the system property {@code nbjunit.hard.timeout} is set to a number |
| * (of milliseconds) by which the whole VM must exit (as in the {@code timeout} |
| * property to Ant's {@code <junit>} task), |
| * this "soft timeout" will default to some portion of the remaining time, so |
| * that we can capture a meaningful thread dump rather than simply report that the |
| * test took too long. (If the VM crashes, the hard timeout kicks in.) Otherwise the |
| * default is 0 (no soft timeout). For an Ant-based NBM project, {@code common.xml} |
| * specifies 600000 (ten minutes) as a default hard timeout, and sets the system |
| * property, so soft timeouts should be the default. |
| * @return amount ms to give one test to finish or 0 to disable time outs |
| * @since 1.20 |
| */ |
| protected int timeOut() { |
| DEFAULT_TIME_OUT_CALLED.set(true); |
| return 0; |
| } |
| private int computeTimeOut() { |
| if (vmDeadline == -1L) { |
| return 0; |
| } |
| Boolean prev = DEFAULT_TIME_OUT_CALLED.get(); |
| try { |
| DEFAULT_TIME_OUT_CALLED.set(null); |
| int tm = timeOut(); |
| if (!Boolean.TRUE.equals(DEFAULT_TIME_OUT_CALLED.get())) { |
| return tm; |
| } |
| } finally { |
| DEFAULT_TIME_OUT_CALLED.set(prev); |
| } |
| |
| int remaining = (int) (vmDeadline - System.currentTimeMillis()); |
| if (remaining > 1500) { |
| return (remaining - 1000) / 2; |
| } |
| return 1500; |
| } |
| |
| /** |
| * Allows easy collecting of log messages send thru java.util.logging API. |
| * Overwrite and return the log level to collect logs to logging file. |
| * If the method returns non-null level, then the level is assigned to |
| * the <code>Logger.getLogger({@linkplain #logRoot logRoot()})</code> and the messages reported to it |
| * are then send into regular log file (which is accessible thru {@link NbTestCase#getLog}) |
| * and in case of failure the last few messages is also included |
| * in <code>failure.getMessage()</code>. |
| * |
| * @return default implementation returns <code>null</code> which disables any logging |
| * support in test |
| * @since 1.27 |
| * @see Log#enable |
| */ |
| protected Level logLevel() { |
| return null; |
| } |
| |
| /** |
| * If overriding {@link #logLevel}, may override this as well to collect messages from only some code. |
| * @return {@code ""} (default) to collect messages from all loggers; or {@code "my.pkg"} or {@code "my.pkg.Class"} etc. |
| * @since 1.68 |
| */ |
| protected String logRoot() { |
| return ""; |
| } |
| |
| /** |
| * Runs the test case, while conditionally skip some according to result of |
| * {@link #canRun} method. |
| */ |
| @Override |
| public void run(final TestResult result) { |
| if (canRun()) { |
| System.setProperty("netbeans.full.hack", "true"); // NOI18N |
| System.setProperty("java.util.prefs.PreferencesFactory", |
| MemoryPreferencesFactory.class.getName());//NOI18N |
| try { |
| Preferences.userRoot().sync(); |
| } catch(BackingStoreException bex) {} |
| Level lev = logLevel(); |
| if (lev != null) { |
| Log.configure(lev, logRoot(), NbTestCase.this); |
| } |
| super.run(result); |
| } |
| } |
| |
| private static StringBuilder printThreadInfo(ThreadInfo ti, StringBuilder sb) { |
| // print thread information |
| printThread(ti, sb); |
| |
| // print stack trace with locks |
| StackTraceElement[] stacktrace = ti.getStackTrace(); |
| MonitorInfo[] monitors = ti.getLockedMonitors(); |
| for (int i = 0; i < stacktrace.length; i++) { |
| StackTraceElement ste = stacktrace[i]; |
| sb.append("\t at " + ste.toString()).append("\n"); |
| for (MonitorInfo mi : monitors) { |
| if (mi.getLockedStackDepth() == i) { |
| sb.append("\t - locked " + mi).append("\n"); |
| } |
| } |
| } |
| sb.append("\n"); |
| return sb; |
| } |
| |
| private static StringBuilder printThread(ThreadInfo ti, StringBuilder sb) { |
| sb.append("\"" + ti.getThreadName() + "\"" + " Id=" |
| + ti.getThreadId() + " in " + ti.getThreadState()); |
| if (ti.getLockName() != null) { |
| sb.append(" waiting on lock=" + ti.getLockName()); |
| } |
| if (ti.isSuspended()) { |
| sb.append(" (suspended)"); |
| } |
| if (ti.isInNative()) { |
| sb.append(" (running in native)"); |
| } |
| sb.append("\n"); |
| if (ti.getLockOwnerName() != null) { |
| sb.append("\t owned by " + ti.getLockOwnerName() + " Id=" |
| + ti.getLockOwnerId()).append("\n"); |
| } |
| return sb; |
| } |
| |
| private static void printMonitorInfo(ThreadInfo ti, MonitorInfo[] monitors, StringBuilder sb) { |
| sb.append("\tLocked monitors: count = " + monitors.length).append("\n"); |
| for (MonitorInfo mi : monitors) { |
| sb.append("\t - " + mi + " locked at ").append("\n"); |
| sb.append("\t " + mi.getLockedStackDepth() + " " |
| + mi.getLockedStackFrame()).append("\n"); |
| } |
| } |
| |
| private static void printLockInfo(LockInfo[] locks, StringBuilder sb) { |
| sb.append("\tLocked synchronizers: count = " + locks.length).append("\n"); |
| for (LockInfo li : locks) { |
| sb.append("\t - " + li).append("\n"); |
| } |
| sb.append("\n"); |
| } |
| |
| private static String threadDump() { |
| ThreadMXBean tmx = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); |
| |
| ThreadInfo[] threads = tmx.dumpAllThreads(tmx.isSynchronizerUsageSupported(), tmx.isObjectMonitorUsageSupported()); |
| StringBuilder sb = new StringBuilder(); |
| |
| for (ThreadInfo ti : threads) { |
| printThreadInfo(ti, sb); |
| } |
| |
| long[] lockedThreads = tmx.isSynchronizerUsageSupported() ? tmx.findDeadlockedThreads() : null; |
| long[] monitorLockedThreads = tmx.findMonitorDeadlockedThreads(); |
| |
| if (lockedThreads != null) { |
| sb.append("\n================\nDead-locked threads:\n"); |
| ThreadInfo[] infos = tmx.getThreadInfo(lockedThreads, true, tmx.isObjectMonitorUsageSupported()); |
| for (ThreadInfo ti : infos) { |
| printThreadInfo(ti, sb); |
| printLockInfo(ti.getLockedSynchronizers(), sb); |
| sb.append("\n"); |
| } |
| } else if (monitorLockedThreads != null) { |
| ThreadInfo[] infos = tmx.getThreadInfo(monitorLockedThreads, Integer.MAX_VALUE); |
| for (ThreadInfo ti : infos) { |
| // print thread information |
| printThread(ti, sb); |
| printMonitorInfo(ti, ti.getLockedMonitors(), sb); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Runs the bare test sequence. It checks {@link #runInEQ} and possibly |
| * schedules the call of <code>setUp</code>, <code>runTest</code> and <code>tearDown</code> |
| * to AWT event thread. It also consults {@link #timeOut} and if so, it starts a |
| * count down and aborts the <code>runTest</code> if the time out expires. |
| * @exception Throwable if any exception is thrown |
| */ |
| @Override |
| public void runBare() throws Throwable { |
| abstract class Guard implements Runnable { |
| private boolean finished; |
| private Throwable t; |
| |
| public abstract void doSomething() throws Throwable; |
| |
| public @Override void run() { |
| try { |
| doSomething(); |
| } catch (Throwable thrwn) { |
| if (MethodOrder.isShuffled()) { |
| thrwn = Log.wrapWithAddendum(thrwn, "(executed in shuffle mode, run with -DNbTestCase.order=" + MethodOrder.getSeed() + " to reproduce the order)", true); |
| } |
| this.t = Log.wrapWithMessages(thrwn, getWorkDirPath()); |
| } finally { |
| synchronized (this) { |
| finished = true; |
| notifyAll(); |
| } |
| } |
| } |
| |
| public synchronized void waitFinished() throws Throwable { |
| waitFinished(0); |
| } |
| |
| public synchronized void waitFinished(final int timeout) throws Throwable { |
| long time = timeout; |
| long startTime = System.currentTimeMillis(); |
| while (!finished){ |
| try { |
| wait(time); |
| if (timeout > 0) { |
| time = timeout - (System.currentTimeMillis() - startTime); |
| if (time < 1) { |
| break; |
| } |
| } |
| } catch (InterruptedException ex) { |
| if (t == null) { |
| t = ex; |
| } |
| } |
| } |
| if (t != null) { |
| throw t; |
| } |
| |
| if (!finished) { |
| throw Log.wrapWithMessages(new AssertionFailedError ("The test " + getName() + " did not finish in " + timeout + "ms\n" + |
| threadDump()) |
| , getWorkDirPath()); |
| } |
| } |
| } |
| /* original sequence from TestCase.runBare(): |
| setUp(); |
| try { |
| runTest(); |
| } finally { |
| tearDown(); |
| } |
| */ |
| // setUp |
| if(runInEQ()) { |
| Guard setUp = new Guard() { |
| public @Override void doSomething() throws Throwable { |
| setUp(); |
| } |
| }; |
| EventQueue.invokeLater(setUp); |
| // need to have timeout because previous test case can block AWT thread |
| setUp.waitFinished(computeTimeOut()); |
| } else { |
| setUp(); |
| } |
| try { |
| // runTest |
| Guard runTest = new Guard() { |
| public @Override void doSomething() throws Throwable { |
| long now = System.nanoTime(); |
| try { |
| runTest(); |
| } catch (Throwable t) { |
| noteWorkDir(workdirNoCreate()); |
| throw noteRandomness(t); |
| } finally { |
| long last = System.nanoTime() - now; |
| if (last < 1) { |
| last = 1; |
| } |
| NbTestCase.this.time = last; |
| } |
| } |
| }; |
| if (runInEQ()) { |
| EventQueue.invokeLater(runTest); |
| runTest.waitFinished(computeTimeOut()); |
| } else { |
| if (computeTimeOut() == 0) { |
| // Regular test. |
| runTest.run(); |
| runTest.waitFinished(); |
| } else { |
| // Regular test with time out |
| Thread watchDog = new Thread(runTest, "Test Watch Dog: " + getName()); |
| watchDog.start(); |
| runTest.waitFinished(computeTimeOut()); |
| } |
| } |
| } finally { |
| // tearDown |
| if(runInEQ()) { |
| Guard tearDown = new Guard() { |
| public @Override void doSomething() throws Throwable { |
| tearDown(); |
| } |
| }; |
| EventQueue.invokeLater(tearDown); |
| // need to have timeout because test can block AWT thread |
| tearDown.waitFinished(computeTimeOut()); |
| } else { |
| tearDown(); |
| } |
| } |
| } |
| /** |
| * Make a note of the working directory for a failed test. |
| * If running inside Hudson, archive it and show the presumed artifact location. |
| */ |
| private void noteWorkDir(File wd) { |
| if (!wd.isDirectory()) { |
| return; |
| } |
| try { |
| String buildURL = System.getenv("BUILD_URL"); |
| if (buildURL != null) { |
| String workspace = new File(System.getenv("WORKSPACE")).getCanonicalPath(); |
| if (!workspace.endsWith(File.separator)) { |
| workspace += File.separator; |
| } |
| String path = wd.getCanonicalPath(); |
| if (path.startsWith(workspace)) { |
| copytree(wd, new File(wd.getParentFile(), wd.getName() + "-FAILED")); |
| System.err.println("Working directory: " + buildURL + "artifact/" + |
| path.substring(workspace.length()).replace(File.separatorChar, '/') + "-FAILED/"); |
| return; |
| } |
| } |
| System.err.println("Working directory: " + wd); |
| } catch (Exception x) { |
| x.printStackTrace(); // do not mask real error |
| } |
| } |
| static void copytree(File from, File to) throws IOException { |
| if (from.isDirectory()) { |
| if (!to.mkdirs()) { |
| throw new IOException("mkdir: " + to); |
| } |
| for (File f : from.listFiles()) { |
| copytree(f, new File(to, f.getName())); |
| } |
| } else { |
| InputStream is = new FileInputStream(from); |
| try { |
| OutputStream os = new FileOutputStream(to); |
| try { |
| // XXX using FileChannel would be more efficient, but more complicated |
| BufferedInputStream bis = new BufferedInputStream(is); |
| BufferedOutputStream bos = new BufferedOutputStream(os); |
| int c; |
| while ((c = bis.read()) != -1) { |
| bos.write(c); |
| } |
| bos.flush(); |
| bos.close(); |
| } finally { |
| os.close(); |
| } |
| } finally { |
| is.close(); |
| } |
| } |
| } |
| private Throwable noteRandomness(Throwable t) { |
| Class<?> c = getClass(); |
| if (c.isAnnotationPresent(RandomlyFails.class)) { |
| return Log.wrapWithAddendum(t, "(" + c.getSimpleName() + " marked @RandomlyFails so try just running test again)", false); |
| } |
| try { |
| if (c.getMethod(getName()).isAnnotationPresent(RandomlyFails.class)) { |
| return Log.wrapWithAddendum(t, "(" + c.getSimpleName() + "." + getName() + " marked @RandomlyFails so try just running test again)", false); |
| } |
| } catch (NoSuchMethodException x) {} |
| return t; |
| // XXX would be nice to actually try to rerun the test (but would make runBare more complicated) |
| } |
| |
| /** Parses the test name to find out whether it encodes a number. The |
| * testSomeName1343 represents number 1343. |
| * @return the number |
| * @exception may throw AssertionFailedError if the number is not found in the test name |
| */ |
| protected final int getTestNumber() { |
| try { |
| Matcher m = Pattern.compile("test[a-zA-Z]*([0-9]+)").matcher(getName()); |
| assertTrue("Name does not contain numbers: " + getName(), m.find()); |
| return Integer.valueOf(m.group(1)).intValue(); |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail("Name: " + getName() + " does not represent number"); |
| return 0; |
| } |
| } |
| |
| |
| /** in nanoseconds */ |
| final long getExecutionTime() { |
| return time; |
| } |
| |
| // additional asserts !!!! |
| |
| |
| /** |
| * Asserts that two files are the same (their content is identical), when files |
| * differ {@link org.netbeans.junit.AssertionFileFailedError AssertionFileFailedError} exception is thrown. |
| * Depending on the Diff implementation additional output can be generated to the file/dir specified by the |
| * <b>diff</b> param. |
| * @param message the detail message for this assertion |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines |
| * the correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| * @param externalDiff instance of class implementing the {@link org.netbeans.junit.diff.Diff} interface, it has to be |
| * already initialized, when passed in this assertFile function. |
| */ |
| static public void assertFile(String message, String test, String pass, String diff, Diff externalDiff) { |
| Diff diffImpl = null == externalDiff ? Manager.getSystemDiff() : externalDiff; |
| File diffFile = getDiffName(pass, null == diff ? null : new File(diff)); |
| |
| if (null == diffImpl) { |
| fail("diff is not available"); |
| } else { |
| try { |
| if (null == diffFile) { |
| if (diffImpl.diff(test, pass, null)) { |
| throw new AssertionFileFailedError(message, ""); |
| } |
| } else { |
| if (diffImpl.diff(test, pass, diffFile.getAbsolutePath())) { |
| throw new AssertionFileFailedError(message, diffFile.getAbsolutePath()); |
| } |
| } |
| } catch (IOException e) { |
| fail("exception in assertFile : " + e.getMessage()); |
| } |
| } |
| } |
| /** |
| * Asserts that two files are the same, it uses specific {@link org.netbeans.junit.diff.Diff Diff} implementation to |
| * compare two files and stores possible differences in the output file. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| * @param externalDiff instance of class implementing the {@link org.netbeans.junit.diff.Diff} interface, it has to be |
| * already initialized, when passed in this assertFile function. |
| */ |
| static public void assertFile(String test, String pass, String diff, Diff externalDiff) { |
| assertFile(null, test, pass, diff, externalDiff); |
| } |
| /** |
| * Asserts that two files are the same, it compares two files and stores possible differences |
| * in the output file, the message is displayed when assertion fails. |
| * @param message the detail message for this assertion |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| */ |
| static public void assertFile(String message, String test, String pass, String diff) { |
| assertFile(message, test, pass, diff, null); |
| } |
| /** |
| * Asserts that two files are the same, it compares two files and stores possible differences |
| * in the output file. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| */ |
| static public void assertFile(String test, String pass, String diff) { |
| assertFile(null, test, pass, diff, null); |
| } |
| /** |
| * Asserts that two files are the same, it just compares two files and doesn't produce any additional output. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| */ |
| static public void assertFile(String test, String pass) { |
| assertFile(null, test, pass, null, null); |
| } |
| |
| /** |
| * Asserts that two files are the same (their content is identical), when files |
| * differ {@link org.netbeans.junit.AssertionFileFailedError AssertionFileFailedError} exception is thrown. |
| * Depending on the Diff implementation additional output can be generated to the file/dir specified by the |
| * <b>diff</b> param. |
| * @param message the detail message for this assertion |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines |
| * the correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| * @param externalDiff instance of class implementing the {@link org.netbeans.junit.diff.Diff} interface, it has to be |
| * already initialized, when passed in this assertFile function. |
| */ |
| static public void assertFile(String message, File test, File pass, File diff, Diff externalDiff) { |
| Diff diffImpl = null == externalDiff ? Manager.getSystemDiff() : externalDiff; |
| File diffFile = getDiffName(pass.getAbsolutePath(), diff); |
| |
| /* |
| System.out.println("NbTestCase.assertFile(): diffFile="+diffFile); |
| System.out.println("NbTestCase.assertFile(): diffImpl="+diffImpl); |
| System.out.println("NbTestCase.assertFile(): externalDiff="+externalDiff); |
| */ |
| |
| if (null == diffImpl) { |
| fail("diff is not available"); |
| } else { |
| try { |
| if (diffImpl.diff(test, pass, diffFile)) { |
| throw new AssertionFileFailedError(message, null == diffFile ? "" : diffFile.getAbsolutePath()); |
| } |
| } catch (IOException e) { |
| fail("exception in assertFile : " + e.getMessage()); |
| } |
| } |
| } |
| /** |
| * Asserts that two files are the same, it uses specific {@link org.netbeans.junit.diff.Diff Diff} implementation to |
| * compare two files and stores possible differences in the output file. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| * @param externalDiff instance of class implementing the {@link org.netbeans.junit.diff.Diff} interface, it has to be |
| * already initialized, when passed in this assertFile function. |
| */ |
| static public void assertFile(File test, File pass, File diff, Diff externalDiff) { |
| assertFile(null, test, pass, diff, externalDiff); |
| } |
| /** |
| * Asserts that two files are the same, it compares two files and stores possible differences |
| * in the output file, the message is displayed when assertion fails. |
| * @param message the detail message for this assertion |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| */ |
| static public void assertFile(String message, File test, File pass, File diff) { |
| assertFile(message, test, pass, diff, null); |
| } |
| /** |
| * Asserts that two files are the same, it compares two files and stores possible differences |
| * in the output file. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| * @param diff file, where differences will be stored, when null differences will not be stored. In case |
| * it points to directory the result file name is constructed from the <b>pass</b> argument and placed to that |
| * directory. Constructed file name consists from the name of pass file (without extension and path) appended |
| * by the '.diff'. |
| */ |
| static public void assertFile(File test, File pass, File diff) { |
| assertFile(null, test, pass, diff, null); |
| } |
| /** |
| * Asserts that two files are the same, it just compares two files and doesn't produce any additional output. |
| * @param test first file to be compared, by the convention this should be the test-generated file |
| * @param pass second file to be compared, it should be so called 'golden' file, which defines the |
| * correct content for the test-generated file. |
| */ |
| static public void assertFile(File test, File pass) { |
| assertFile("Difference between " + test + " and " + pass, test, pass, null, null); |
| } |
| |
| /** |
| */ |
| static private File getDiffName(String pass, File diff) { |
| if (null == diff) { |
| return null; |
| } |
| |
| if (!diff.exists() || diff.isFile()) { |
| return diff; |
| } |
| |
| StringBuilder d = new StringBuilder(); |
| int i1, i2; |
| |
| d.append(diff.getAbsolutePath()); |
| i1 = pass.lastIndexOf('\\'); |
| i2 = pass.lastIndexOf('/'); |
| i1 = i1 > i2 ? i1 : i2; |
| i1 = -1 == i1 ? 0 : i1 + 1; |
| |
| i2 = pass.lastIndexOf('.'); |
| i2 = -1 == i2 ? pass.length() : i2; |
| |
| if (0 < d.length()) { |
| d.append("/"); |
| } |
| |
| d.append(pass.substring(i1, i2)); |
| d.append(".diff"); |
| return new File(d.toString()); |
| } |
| |
| // methods for work with tests' workdirs |
| |
| |
| /** Returns path to test method working directory as a String. Path is constructed |
| * as ${nbjunit.workdir}/${package}.${classname}/${testmethodname}. (The nbjunit.workdir |
| * property should be set in junit.properties; otherwise the default is ${java.io.tmpdir}/tests.) |
| * Please note that this method does not guarantee that the working directory really exists. |
| * @return a path to a test method working directory |
| */ |
| public String getWorkDirPath() { |
| if (workDirPath != null) { |
| return workDirPath; |
| } |
| |
| String name = getName(); |
| // start - PerformanceTestCase overrides getName() method and then |
| // name can contain illegal characters |
| String osName = System.getProperty("os.name"); |
| if (osName != null && osName.startsWith("Windows")) { |
| char ntfsIllegal[] ={'"','/','\\','?','<','>','|',':'}; |
| for (int i=0; i<ntfsIllegal.length; i++) { |
| name = name.replace(ntfsIllegal[i], '~'); |
| } |
| } |
| // end |
| |
| // #94319 - shorten workdir path if the following is too long |
| // "Manager.getWorkDirPath()+File.separator+getClass().getName()+File.separator+name" |
| int len1 = Manager.getWorkDirPath().length(); |
| String clazz = getClass().getName(); |
| int len2 = clazz.length(); |
| int len3 = name.length(); |
| |
| int tooLong = Integer.getInteger("nbjunit.too.long", 100); |
| if (len1 + len2 + len3 > tooLong) { |
| clazz = abbrevDots(clazz); |
| len2 = clazz.length(); |
| } |
| |
| if (len1 + len2 + len3 > tooLong) { |
| name = abbrevCapitals(name); |
| } |
| |
| String p = Manager.getWorkDirPath() + File.separator + clazz + File.separator + name; |
| String realP; |
| |
| for (int i = 0; ; i++) { |
| realP = i == 0 ? p : p + "-" + i; |
| if (usedPaths.add(realP)) { |
| break; |
| } |
| } |
| |
| |
| workDirPath = realP; |
| return realP; |
| } |
| |
| private static Set<String> usedPaths = new HashSet<String>(); |
| |
| private static String abbrevDots(String dotted) { |
| StringBuilder sb = new StringBuilder(); |
| String sep = ""; |
| for (String item : dotted.split("\\.")) { |
| sb.append(sep); |
| sb.append(item.charAt(0)); |
| sep = "."; |
| } |
| return sb.toString(); |
| } |
| |
| private static String abbrevCapitals(String name) { |
| if (name.startsWith("test")) { |
| name = name.substring(4); |
| } |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < name.length(); i++) { |
| if (Character.isUpperCase(name.charAt(i))) { |
| sb.append(Character.toLowerCase(name.charAt(i))); |
| } |
| } |
| if (sb.length() == 0) { |
| // for names without uppercase (e.g. test12345, test_a) |
| return name; |
| } else { |
| return sb.toString(); |
| } |
| } |
| |
| private File workdirNoCreate() { |
| return Manager.normalizeFile(new File(getWorkDirPath())); |
| } |
| |
| /** Returns unique working directory for a test (each test method has a unique dir). |
| * If not available, method tries to create it. This method uses {@link #getWorkDirPath} |
| * method to determine the unique path. |
| * <p><strong>Warning:</strong> the working directory is <em>not</em> guaranteed |
| * to be empty when you get it, so if this is being called in {@link #setUp} you |
| * are strongly advised to first call {@link #clearWorkDir} to ensure that each |
| * test run starts with a clean slate.</p> |
| * @throws IOException if the directory cannot be created |
| * @return file to the working directory directory |
| */ |
| public File getWorkDir() throws IOException { |
| // construct path from workdir classpath + classname + methodname |
| |
| /* |
| String path = this.getClass().getResource("").getFile().toString(); |
| String srcElement="src"; |
| String workdirElement="workdir"; |
| int srcStart = path.lastIndexOf(srcElement); |
| // base path |
| path = path.substring(0,srcStart)+workdirElement; |
| // package+class |
| path += "/"+this.getClass().getName().replace('.','/'); |
| // method name |
| path += "/"+getName(); |
| */ |
| |
| // new way how to get path - from defined property + classname +methodname |
| |
| |
| |
| // now we have path, so if not available, create workdir |
| File workdir = workdirNoCreate(); |
| if (workdir.exists()) { |
| if (!workdir.isDirectory()) { |
| // work dir exists, but is not directory - this should not happen |
| // trow exception |
| throw new IOException("workdir exists, but is not a directory, workdir = " + workdir); |
| } else { |
| // everything looks correctly, return the path |
| return workdir; |
| } |
| } else { |
| // we need to create it |
| boolean result = workdir.mkdirs(); |
| if (result == false) { |
| // mkdirs() failed - throw an exception |
| throw new IOException("workdir creation failed: " + workdir); |
| } else { |
| // everything looks ok - return path |
| return workdir; |
| } |
| } |
| } |
| |
| // private method for deleting a file/directory (and all its subdirectories/files) |
| private static void deleteFile(File file) throws IOException { |
| if (file.isDirectory() && file.equals(file.getCanonicalFile())) { |
| // file is a directory - delete sub files first |
| File files[] = file.listFiles(); |
| for (int i = 0; i < files.length; i++) { |
| deleteFile(files[i]); |
| } |
| |
| } |
| // file is a File :-) |
| boolean result = file.delete(); |
| if (result == false ) { |
| // a problem has appeared |
| throw new IOException("Cannot delete file, file = "+file.getPath()); |
| } |
| } |
| |
| // private method for deleting every subfiles/subdirectories of a file object |
| static void deleteSubFiles(File file) throws IOException { |
| File files[] = file.getCanonicalFile().listFiles(); |
| if (files != null) { |
| for (File f : files) { |
| deleteFile(f); |
| } |
| } else { |
| // probably do nothing - file is not a directory |
| } |
| } |
| |
| /** Deletes all files including subdirectories in test's working directory. |
| * @throws IOException if any problem has occured during deleting files/directories |
| */ |
| public void clearWorkDir() throws IOException { |
| synchronized (logStreamTable) { |
| File workdir = getWorkDir(); |
| closeAllStreams(); |
| deleteSubFiles(workdir); |
| } |
| } |
| |
| private String lastTestMethod=null; |
| |
| private boolean hasTestMethodChanged() { |
| if (!this.getName().equals(lastTestMethod)) { |
| lastTestMethod=this.getName(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // hashtable holding all already used logs and correspondig printstreams |
| private final Map<String,PrintStream> logStreamTable = new HashMap<String,PrintStream>(); |
| |
| private PrintStream getFileLog(String logName) throws IOException { |
| synchronized (logStreamTable) { |
| if (hasTestMethodChanged()) { |
| // we haven't used logging capability - create hashtables |
| closeAllStreams(); |
| } else { |
| if (logStreamTable.containsKey(logName)) { |
| //System.out.println("Getting stream from cache:"+logName); |
| return logStreamTable.get(logName); |
| } |
| } |
| // we didn't used this log, so let's create it |
| OutputStream fileLog = new WFOS(new File(getWorkDir(),logName)); |
| PrintStream printStreamLog = new PrintStream(fileLog,true); |
| logStreamTable.put(logName,printStreamLog); |
| //System.out.println("Created new stream:"+logName); |
| return printStreamLog; |
| } |
| } |
| |
| private void closeAllStreams() { |
| for (PrintStream ps : logStreamTable.values()) { |
| ps.close(); |
| } |
| logStreamTable.clear(); |
| } |
| |
| private static class WFOS extends FilterOutputStream { |
| private File f; |
| private int bytes; |
| |
| public WFOS(File f) throws FileNotFoundException { |
| super(new FileOutputStream(f)); |
| this.f = f; |
| } |
| |
| @Override |
| public void write(byte[] b, int off, int len) throws IOException { |
| add(len); |
| out.write(b, off, len); |
| } |
| |
| @Override |
| public void write(byte[] b) throws IOException { |
| add(b.length); |
| out.write(b); |
| } |
| |
| @Override |
| public void write(int b) throws IOException { |
| add(1); |
| out.write(b); |
| } |
| |
| private synchronized void add(int i) throws IOException { |
| bytes += i; |
| if (bytes >= 1048576L) { // 1mb |
| out.close(); |
| File trim = new File(f.getParent(), "TRIMMED_" + f.getName()); |
| trim.delete(); |
| f.renameTo(trim); |
| f.delete(); |
| out = new FileOutputStream(f); |
| bytes = 0; |
| } |
| } |
| |
| |
| } // end of WFOS |
| |
| // private PrintStream wrapper for System.out |
| PrintStream systemOutPSWrapper = new PrintStream(System.out); |
| |
| /** Returns named log stream. If log cannot be created as a file in the |
| * testmethod working directory, PrintStream created from System.out is used. Please |
| * note, that tests shoudn't call log.close() method, unless they really don't want |
| * to use this log anymore. |
| * @param logName name of the log - file in the working directory |
| * @return Log PrintStream |
| */ |
| public PrintStream getLog(String logName) { |
| try { |
| return getFileLog(logName); |
| } catch (IOException ioe) { |
| /// hey, file is not available - log will be made to System.out |
| // we should probably write a little note about it |
| //System.err.println("Test method "+this.getName()+" - cannot open file log to file:"+logName |
| // +" - defaulting to System.out"); |
| return systemOutPSWrapper; |
| } |
| } |
| |
| /** Return default log named as ${testmethod}.log. If the log cannot be created |
| * as a file in testmethod working directory, PrinterStream to System.out is returned |
| * @return log |
| */ |
| public PrintStream getLog() { |
| return getLog(this.getName()+".log"); |
| } |
| |
| /** Simple and easy to use method for printing a message to a default log |
| * @param message meesage to log |
| */ |
| public void log(String message) { |
| getLog().println(message); |
| } |
| |
| |
| /** Easy to use method for logging a message to a named log |
| * @param log which log to use |
| * @param message message to log |
| */ |
| public void log(String log, String message) { |
| getLog(log).println(message); |
| } |
| |
| // reference file stuff ... |
| |
| |
| /** Get PrintStream to log inteded for reference files comparision. Reference |
| * log is stored as a file named ${testmethod}.ref in test method working directory. |
| * If the file cannot be created, the testcase will automatically fail. |
| * @return PrintStream to referencing log |
| */ |
| public PrintStream getRef() { |
| String refFilename = this.getName()+".ref"; |
| try { |
| return getFileLog(refFilename); |
| } catch (IOException ioe) { |
| // canot get ref file - return system.out |
| //System.err.println("Test method "+this.getName()+" - cannot open ref file:"+refFilename |
| // +" - defaulting to System.out and failing test"); |
| fail("Could not open reference file: "+refFilename); |
| return systemOutPSWrapper; |
| } |
| } |
| |
| /** Easy to use logging method for printing a message to a reference log. |
| * @param message message to log |
| */ |
| public void ref(String message) { |
| getRef().println(message); |
| } |
| |
| /** Get the test method specific golden file from ${xtest.data}/goldenfiles/${classname} |
| * directory. If not found, try also deprecated src/data/goldenfiles/${classname} |
| * resource directory. |
| * @param filename filename to get from golden files directory |
| * @return golden file |
| */ |
| public File getGoldenFile(String filename) { |
| String fullClassName = this.getClass().getName(); |
| String goldenFileName = fullClassName.replace('.', '/')+"/"+filename; |
| // golden files are in ${xtest.data}/goldenfiles/${classname}/... |
| File goldenFile = new File(getDataDir()+"/goldenfiles/"+goldenFileName); |
| if(goldenFile.exists()) { |
| // Return if found, otherwise try to find golden file in deprecated |
| // location. When deprecated part is removed, add assertTrue(goldenFile.exists()) |
| // instead of if clause. |
| return goldenFile; |
| } |
| |
| /** Deprecated - this part is deprecated */ |
| // golden files are in data/goldenfiles/${classname}/* ... |
| String className = fullClassName; |
| int lastDot = fullClassName.lastIndexOf('.'); |
| if (lastDot != -1) { |
| className = fullClassName.substring(lastDot+1); |
| } |
| goldenFileName = className+"/"+filename; |
| URL url = this.getClass().getResource("data/goldenfiles/"+goldenFileName); |
| assertNotNull("Golden file not found in any of the following locations:\n "+ |
| goldenFile+"\n "+ |
| "src/"+fullClassName.replace('.', '/').substring(0, fullClassName.indexOf(className))+"data/goldenfiles/"+goldenFileName, |
| url); |
| String resString = convertNBFSURL(url); |
| goldenFile = new File(resString); |
| return goldenFile; |
| /** Deprecated end. */ |
| } |
| |
| /** Returns pointer to directory with test data (golden files, sample files, ...). |
| * It is the same from xtest.data property. |
| * @return data directory |
| */ |
| public File getDataDir() { |
| // XXX should this be deprecated? |
| String xtestData = System.getProperty("xtest.data"); |
| if(xtestData != null) { |
| return Manager.normalizeFile(new File(xtestData)); |
| } else { |
| // property not set => try to find it |
| URL codebase = getClass().getProtectionDomain().getCodeSource().getLocation(); |
| if (!codebase.getProtocol().equals("file")) { |
| throw new Error("Cannot find data directory from " + codebase); |
| } |
| File dataDir; |
| try { |
| dataDir = new File(new File(codebase.toURI()).getParentFile(), "data"); |
| } catch (URISyntaxException x) { |
| throw new Error(x); |
| } |
| return Manager.normalizeFile(dataDir); |
| } |
| } |
| |
| /** Get the default testmethod specific golden file from |
| * data/goldenfiles/${classname}/${testmethodname}.pass |
| * @return filename to get from golden files resource directory |
| */ |
| public File getGoldenFile() { |
| return getGoldenFile(this.getName()+".pass"); |
| } |
| |
| |
| /** Compares golden file and reference log. If both files are the |
| * same, test passes. If files differ, test fails and diff file is |
| * created (diff is created only when using native diff, for details |
| * see JUnit module documentation) |
| * @param testFilename reference log file name |
| * @param goldenFilename golden file name |
| * @param diffFilename diff file name (optional, if null, then no diff is created) |
| */ |
| public void compareReferenceFiles(String testFilename, String goldenFilename, String diffFilename) { |
| try { |
| if (!getRef().equals(systemOutPSWrapper)) { |
| // better flush the reference file |
| getRef().flush(); |
| getRef().close(); |
| } |
| File goldenFile = getGoldenFile(goldenFilename); |
| File testFile = new File(getWorkDir(),testFilename); |
| File diffFile = new File(getWorkDir(),diffFilename); |
| String message = "Files differ"; |
| if(System.getProperty("xtest.home") == null) { |
| // show location of diff file only when run without XTest (run file in IDE) |
| message += "; check "+diffFile; |
| } |
| assertFile(message, testFile, goldenFile, diffFile); |
| } catch (IOException ioe) { |
| fail("Could not obtain working direcory"); |
| } |
| } |
| |
| /** Compares default golden file and default reference log. If both files are the |
| * same, test passes. If files differ, test fails and default diff (${methodname}.diff) |
| * file is created (diff is created only when using native diff, for details |
| * see JUnit module documentation) |
| */ |
| public void compareReferenceFiles() { |
| compareReferenceFiles(this.getName()+".ref",this.getName()+".pass",this.getName()+".diff"); |
| } |
| |
| // utility stuff for getting resources from NetBeans' filesystems |
| |
| /** Converts NetBeans filesystem URL to absolute path. |
| * @param url URL to convert |
| * @return absolute path |
| * @deprecated No longer applicable as of NB 4.0 at the latest. |
| * <code>FileObject.getURL()</code> should be returning a <code>file</code>-protocol |
| * URL, which can be converted to a disk path using <code>new File(URI)</code>; or |
| * use <code>FileUtil.toFile</code>. |
| */ |
| @Deprecated |
| public static String convertNBFSURL(URL url) { |
| if(url == null) { |
| throw new IllegalArgumentException("Given URL should not be null."); |
| } |
| String externalForm = url.toExternalForm(); |
| if (externalForm.startsWith("nbfs://")) { |
| // new nbfsurl format (post 06/2003) |
| return convertNewNBFSURL(url); |
| } else { |
| // old nbfsurl (and non nbfs urls) |
| return convertOldNBFSURL(url); |
| } |
| } |
| |
| // radix for new nbfsurl |
| private final static int radix = 16; |
| // new nbfsurl decoder - assumes the external form |
| // begins with nbfs:// |
| private static String convertNewNBFSURL(URL url) { |
| String externalForm = url.toExternalForm(); |
| String path; |
| if (externalForm.startsWith("nbfs://nbhost/")) { |
| // even newer nbfsurl (hope it does not change soon) |
| // return path and omit first slash sign |
| path = url.getPath().substring(1); |
| } else { |
| path = externalForm.substring("nbfs://".length()); |
| } |
| // convert separators (%2f = /, etc.) |
| StringBuilder sb = new StringBuilder(); |
| int i = 0; |
| int len = path.length(); |
| while (i < len) { |
| char ch = path.charAt(i++); |
| if (ch == '%' && (i+1) < len) { |
| char h1 = path.charAt(i++); |
| char h2 = path.charAt(i++); |
| // convert d1+d2 hex number to char |
| ch = (char)Integer.parseInt("" + h1 + h2, radix); |
| |
| } |
| sb.append(ch); |
| } |
| return sb.toString(); |
| |
| } |
| |
| // old nbfsurl decoder |
| private static String convertOldNBFSURL(URL url) { |
| String path = url.getFile(); |
| if(url.getProtocol().equals("nbfs")) { |
| // delete prefix of special Filesystem (e.g. org.netbeans.modules.javacvs.JavaCvsFileSystem) |
| String prefixFS = "FileSystem "; |
| if(path.indexOf(prefixFS)>-1) { |
| path = path.substring(path.indexOf(prefixFS)+prefixFS.length()); |
| } |
| // convert separators ("QB="/" etc.) |
| StringBuilder sb = new StringBuilder(); |
| int i = 0; |
| int len = path.length(); |
| while (i < len) { |
| char ch = path.charAt(i++); |
| if (ch == 'Q' && i < len) { |
| ch = path.charAt(i++); |
| switch (ch) { |
| case 'B': |
| sb.append('/'); |
| break; |
| case 'C': |
| sb.append(':'); |
| break; |
| case 'D': |
| sb.append('\\'); |
| break; |
| case 'E': |
| sb.append('#'); |
| break; |
| default: |
| // not a control sequence |
| sb.append('Q'); |
| sb.append(ch); |
| break; |
| } |
| } else { |
| // not Q |
| sb.append(ch); |
| } |
| } |
| path = sb.toString(); |
| } |
| return path; |
| } |
| |
| |
| /** Asserts that the object can be garbage collected. Tries to GC ref's referent. |
| * @param text the text to show when test fails. |
| * @param ref the referent to object that |
| * should be GCed |
| */ |
| public static void assertGC(String text, Reference<?> ref) { |
| assertGC(text, ref, Collections.emptySet()); |
| } |
| |
| /** Asserts that the object can be garbage collected. Tries to GC ref's referent. |
| * @param text the text to show when test fails. |
| * @param ref the referent to object that should be GCed |
| * @param rootsHint a set of objects that should be considered part of the |
| * rootset for this scan. This is useful if you want to verify that one structure |
| * (usually long living in real application) is not holding another structure |
| * in memory, without setting a static reference to the former structure. |
| * <h3>Example:</h3> |
| * <pre> |
| * // test body |
| * WeakHashMap map = new WeakHashMap(); |
| * Object target = new Object(); |
| * map.put(target, "Val"); |
| * |
| * // verification step |
| * Reference ref = new WeakReference(target); |
| * target = null; |
| * assertGC("WeakMap does not hold the key", ref, Collections.singleton(map)); |
| * </pre> |
| */ |
| public static void assertGC(final String text, final Reference<?> ref, final Set<?> rootsHint) { |
| NbModuleLogHandler.whileIgnoringOOME(new Runnable() { |
| @SuppressWarnings({"SleepWhileHoldingLock", "SleepWhileInLoop"}) |
| public @Override void run() { |
| List<byte[]> alloc = new ArrayList<byte[]>(); |
| int size = 100000; |
| for (int i = 0; i < 50; i++) { |
| if (ref.get() == null) { |
| return; |
| } |
| try { |
| System.gc(); |
| } catch (OutOfMemoryError error) { |
| // OK |
| } |
| try { |
| System.runFinalization(); |
| } catch (OutOfMemoryError error) { |
| // OK |
| } |
| try { |
| alloc.add(new byte[size]); |
| size = (int)(((double)size) * 1.3); |
| } catch (OutOfMemoryError error) { |
| size = size / 2; |
| } |
| try { |
| if (i % 3 == 0) { |
| Thread.sleep(321); |
| } |
| } catch (InterruptedException t) { |
| // ignore |
| } |
| } |
| alloc = null; |
| String str = null; |
| try { |
| str = findRefsFromRoot(ref.get(), rootsHint); |
| } catch (Exception e) { |
| throw new AssertionFailedErrorException(e); |
| } catch (OutOfMemoryError err) { |
| // OK |
| } |
| fail(text + ":\n" + str); |
| } |
| }); |
| } |
| |
| /** Assert size of some structure. Traverses the whole reference |
| * graph of objects accessible from given root object and check its size |
| * against the limit. |
| * @param message the text to show when test fails. |
| * @param limit maximal allowed heap size of the structure |
| * @param root the root object from which to traverse |
| */ |
| public static void assertSize(String message, int limit, Object root ) { |
| assertSize(message, Arrays.asList( new Object[] {root} ), limit); |
| } |
| |
| /** Assert size of some structure. Traverses the whole reference |
| * graph of objects accessible from given roots and check its size |
| * against the limit. |
| * @param message the text to show when test fails. |
| * @param roots the collection of root objects from which to traverse |
| * @param limit maximal allowed heap size of the structure |
| */ |
| public static void assertSize(String message, Collection<?> roots, int limit) { |
| assertSize(message, roots, limit, new Object[0]); |
| } |
| |
| /** Assert size of some structure. Traverses the whole reference |
| * graph of objects accessible from given roots and check its size |
| * against the limit. |
| * @param message the text to show when test fails. |
| * @param roots the collection of root objects from which to traverse |
| * @param limit maximal allowed heap size of the structure |
| * @param skip Array of objects used as a boundary during heap scanning, |
| * neither these objects nor references from these objects |
| * are counted. |
| */ |
| public static void assertSize(String message, Collection<?> roots, int limit, Object[] skip) { |
| org.netbeans.insane.scanner.Filter f = ScannerUtils.skipObjectsFilter(Arrays.asList(skip), false); |
| assertSize(message, roots, limit, f); |
| } |
| |
| |
| /** Assert size of some structure. Traverses the whole reference |
| * graph of objects accessible from given roots and check its size |
| * against the limit. |
| * @param message the text to show when test fails. |
| * @param roots the collection of root objects from which to traverse |
| * @param limit maximal allowed heap size of the structure |
| * @param skip custom filter for counted objects |
| * @return actual size or <code>-1</code> on internal error. |
| */ |
| public static int assertSize(String message, Collection<?> roots, int limit, final MemoryFilter skip) { |
| org.netbeans.insane.scanner.Filter f = new org.netbeans.insane.scanner.Filter() { |
| public @Override boolean accept(Object o, Object refFrom, Field ref) { |
| return !skip.reject(o); |
| } |
| }; |
| return assertSize(message, roots, limit, f); |
| } |
| |
| private static int assertSize(String message, Collection<?> roots, int limit, |
| org.netbeans.insane.scanner.Filter f) { |
| try { |
| final CountingVisitor counter = new CountingVisitor(); |
| ScannerUtils.scan(f, counter, roots, false); |
| int sum = counter.getTotalSize(); |
| if (sum > limit) { |
| StringBuilder sb = new StringBuilder(4096); |
| sb.append(message); |
| sb.append(": leak ").append(sum - limit).append(" bytes "); |
| sb.append(" over limit of "); |
| sb.append(limit).append(" bytes"); |
| Set<Class<?>> classes = new TreeSet<Class<?>>(new Comparator<Class<?>>() { |
| public @Override int compare(Class<?> c1, Class<?> c2) { |
| int r = counter.getSizeForClass(c2) - counter.getSizeForClass(c1); |
| return r != 0 ? r : c1.hashCode() - c2.hashCode(); |
| } |
| }); |
| classes.addAll(counter.getClasses()); |
| for (Class<?> cls : classes) { |
| if (counter.getCountForClass(cls) == 0) { |
| continue; |
| } |
| sb.append("\n ").append(cls.getName()).append(": "). |
| append(counter.getCountForClass(cls)).append(", "). |
| append(counter.getSizeForClass(cls)).append("B"); |
| } |
| fail(sb.toString()); |
| } |
| return sum; |
| } catch (Exception e) { |
| throw new AssertionFailedErrorException("Could not traverse reference graph", e); |
| } |
| } |
| |
| /** |
| * Fails a test with known bug ID. |
| * @param bugID the bug number according bug report system. |
| */ |
| public static void failByBug(int bugID) { |
| throw new AssertionKnownBugError(bugID); |
| } |
| |
| /** |
| * Fails a test with known bug ID and with the given message. |
| * @param bugID the bug number according bug report system. |
| * @param message the text to show when test fails. |
| */ |
| public static void failByBug(int bugID, String message) { |
| throw new AssertionKnownBugError(bugID, message); |
| } |
| |
| private static String findRefsFromRoot(final Object target, final Set<?> rootsHint) throws Exception { |
| int count = Integer.getInteger("assertgc.paths", 1); |
| StringBuilder sb = new StringBuilder(); |
| final Map<Object,Void> skip = new IdentityHashMap<Object,Void>(); |
| |
| org.netbeans.insane.scanner.Filter knownPath = new org.netbeans.insane.scanner.Filter() { |
| public @Override boolean accept(Object obj, Object referredFrom, Field reference) { |
| return !skip.containsKey(obj); |
| } |
| }; |
| |
| while (count-- > 0) { |
| @SuppressWarnings("unchecked") |
| Map<Object,Path> m = LiveReferences.fromRoots(Collections.singleton(target), (Set<Object>)rootsHint, null, knownPath); |
| Path p = m.get(target); |
| if (p == null) { |
| break; |
| } |
| if (sb.length() > 0) { |
| sb.append("\n\n"); |
| } |
| sb.append(p); |
| for (; p != null; p=p.nextNode()) { |
| Object o = p.getObject(); |
| if (o != target) { |
| skip.put(o, null); |
| } |
| } |
| } |
| return sb.length() > 0 ? sb.toString() : "Not found!!!"; |
| } |
| |
| } |