All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.junit.Log Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import junit.framework.AssertionFailedError;
import static junit.framework.TestCase.fail;
import org.netbeans.junit.internal.NbModuleLogHandler;

/** Collects log messages.
 *
 * @author Jaroslav Tulach
 */
public final class Log extends Handler {
    /** the test that is currently running */
    private static NbTestCase current;
    /** last 40Kb of collected error messages */
    private static final StringBuffer messages = new StringBuffer ();
    /** initial length of messages */
    private static int initialMessages;
    /** stream to log to */
    private Reference log;
    /** logger we are assigned to */
    private Logger logger;

        
    /** Creates a new instance of Log */
    public Log() {
    }

    /** Creates handler with assigned logger
     */
    private Log(Logger l, PrintStream ps) {
        log = new WeakReference(ps);
        logger = l;
    }

    static Runnable internalLog() {
        return new IL(true);
    }

    /** Enables logging for given logger name and given severity.
     * Everything logged to the object is going to go to the returned
     * CharSequence object which can be used to check the content or
     * converted toString.
     * 

* The logging stops when the returned object is garbage collected. * * @param loggerName the name to capture logging for * @param level the level of details one wants to get * @return character sequence which can be check or converted to string * @since 1.27 */ public static CharSequence enable(String loggerName, Level level) { IL il = new IL(false); class MyPs extends PrintStream implements CharSequence { private ByteArrayOutputStream os; public MyPs() { this(new ByteArrayOutputStream()); } private MyPs(ByteArrayOutputStream arr) { super(arr); os = arr; } public int length() { return toString().length(); } public char charAt(int index) { return toString().charAt(index); } public CharSequence subSequence(int start, int end) { return toString().subSequence(start, end); } @Override public String toString() { return os.toString(); } } Logger l = Logger.getLogger(loggerName); if (l.getLevel() == null || l.getLevel().intValue() > level.intValue()) { l.setLevel(level); } MyPs ps = new MyPs(); Log log = new Log(l, ps); log.setLevel(level); l.addHandler(log); return ps; } /** * Can emulate the execution flow of multiple threads in a deterministic * way so it is easy to emulate race conditions or deadlocks just with * the use of additional log messages inserted into the code. *

* The best example showing usage of this method is real life test. * Read FlowControlTest.java to know everything * about the expected usage of this method. *

* The method does listen on output send to a logger listenTo * by various threads and either suspends them or wake them up trying * as best as it can to mimic the log output described in order. * Of course, this may not always be possible, so there is the timeout * value which specifies the maximum time a thread can be suspended while * waiting for a single message. The information about the internal behaviour * of the controlFlow method can be send to reportTo logger, * if provided, so in case of failure one can analyse what went wrong. *

* The format of the order is a set of lines like: *

     * THREAD:name_of_the_thread MSG:message_to_expect
     * 
* which define the order saying that at this time a thread with a given name * is expected to send given message. Both the name of the thread and * the message are regular expressions so one can shorten them by using .* * or any other trick. Btw. the format of the order is similar * to the one logged by the {@link Log#enable} or {@link NbTestCase#logLevel} methods, * so when one gets a test failure with enabled logging, * it is enough to just delete the unnecessary messages, replace too specific * texts like @574904 with .* and the order is * ready for use. * * @param listenTo the logger to listen to and guide the execution according to messages sent to it * @param reportTo the logger to report internal state to or null if the logging is not needed * @param order the string describing the expected execution order of threads * @param timeout the maximal wait time of each thread on given message, zero if the waiting shall be infinite * * @author Jaroslav Tulach, invented during year 2005 * @since 1.28 */ public static void controlFlow(Logger listenTo, Logger reportTo, String order, int timeout) { ControlFlow.registerSwitches(listenTo, reportTo, order, timeout); } /** Starts to listen on given log and collect parameters of messages that * were send to it. This is supposed to be called at the beginning of a test, * to get messages from the programs that use * timers/counters * infrastructure. At the end one should call {@link #assertInstances}. * * * @param log logger to listen on, if null, it uses the standard timers/counters one * @param msg name of messages to collect, if null, all messages will be recorded * @param level level of messages to record * @since 1.44 */ public static void enableInstances(Logger log, String msg, Level level) { if (log == null) { log = Logger.getLogger("TIMER"); // NOI18N } log.addHandler(new InstancesHandler(msg, level)); if (log.getLevel() == null || log.getLevel().intValue() > level.intValue()) { log.setLevel(level); } } /** Assert to verify that all collected instances via {@link #enableInstances} * can disappear. Uses {@link NbTestCase#assertGC} on each of them. * * @param msg message to display in case of potential failure */ public static void assertInstances(String msg) { InstancesHandler.assertGC(msg); } /** Assert to verify that all properly named instances collected via {@link #enableInstances} * can disappear. Uses {@link NbTestCase#assertGC} on each of them. * * @param msg message to display in case of potential failure * @param names list of names of instances to test for and verify that they disappear * @since 1.53 */ public static void assertInstances(String msg, String... names) { InstancesHandler.assertGC(msg, names); } static void configure(Level lev, String root, NbTestCase current) { IL il = new IL(false); String c = "handlers=" + Log.class.getName() + "\n" + root + ".level=" + lev.intValue() + "\n"; ByteArrayInputStream is = new ByteArrayInputStream(c.getBytes()); try { LogManager.getLogManager().readConfiguration(is); } catch (IOException ex) { // exception ex.printStackTrace(); } Log.current = current; Log.messages.setLength(0); Log.messages.append("Starting test "); Log.messages.append(current.getName()); Log.messages.append('\n'); Log.initialMessages = Log.messages.length(); } private PrintStream getLog() { if (log != null) { PrintStream ps = log.get(); if (ps == null) { // gc => remove the handler setLevel(Level.OFF); logger.removeHandler(this); } return ps; } NbTestCase c = current; Runnable off = Log.internalLog(); try { return c == null ? System.err : c.getLog(); } finally { off.run(); } } @Override public void publish(LogRecord record) { if (record.getLevel().intValue() < getLevel().intValue()) { return; } if (IL.isInternalLog()) { return; } Runnable off = internalLog(); try { StringBuffer sb = NbModuleLogHandler.toString(record); PrintStream ps = getLog(); if (ps != null) { try { ps.println(sb.toString()); } catch (LinkageError err) { // prevent circular references } } if (messages.length() + sb.length() > 20000) { if (sb.length() > 20000) { messages.setLength(0); sb.delete(0, sb.length() - 20000); } else { messages.setLength(20000 - sb.length()); } } messages.append(sb.toString()); } finally { off.run(); } } @Override public void flush() { } @Override public void close() { Logger l = this.logger; if (getLevel() != Level.OFF && l != null) { l.addHandler(this); } } static /* @CheckForNull */ String normalizedMessages(String workDirPath) { if (messages.length() == initialMessages) { return null; } return NbModuleLogHandler.normalize(messages, workDirPath); } static Throwable wrapWithMessages(Throwable ex, String workDirPath) { String m = normalizedMessages(workDirPath); if (m == null) { // no wrapping return ex; } return wrapWithAddendum(ex, "Log:\n" + m, true); } static Throwable wrapWithAddendum(Throwable ex, String addendum, boolean after) { if (ex instanceof AssertionFailedError) { AssertionFailedError ne = new AssertionFailedError(combineMessages(ex, addendum, after)); if (ex.getCause() != null) { ne.initCause(ex.getCause()); } ne.setStackTrace (ex.getStackTrace ()); return ne; } if (ex instanceof AssertionError) { // preferred in JUnit 4 AssertionError ne = new AssertionError(combineMessages(ex, addendum, after)); if (ex.getCause() != null) { ne.initCause(ex.getCause()); } ne.setStackTrace(ex.getStackTrace()); return ne; } if (ex instanceof IOException) {//#66208 IOException ne = new IOException(combineMessages(ex, addendum, after)); if (ex.getCause() != null) { ne.initCause(ex.getCause()); } ne.setStackTrace (ex.getStackTrace ()); return ne; } if (ex instanceof Exception) { return new InvocationTargetException(ex, combineMessages(ex, addendum, after)); } return ex; } private static String combineMessages(Throwable ex, String addendum, boolean after) { String baseMessage = ex.getMessage(); return (baseMessage == null || baseMessage.equals("null")) ? addendum : after ? baseMessage + " " + addendum : addendum + " " + baseMessage; } private static class InstancesHandler extends Handler { static final Map instances = Collections.synchronizedMap(new WeakHashMap()); static int cnt; private final String msg; public InstancesHandler(String msg, Level level) { setLevel(level); this.msg = msg; } @Override public void publish(LogRecord record) { Object[] param = record.getParameters(); if (param == null) { return; } if (msg != null && !msg.equals(record.getMessage())) { return; } cnt++; for (Object o : param) { instances.put(o, record.getMessage()); } } @Override public void flush() { } @Override public void close() throws SecurityException { } public static void assertGC(String msg, String... names) { AssertionFailedError t = null; List refs = new ArrayList(); List txts = new ArrayList(); int count = 0; Set nameSet = names == null || names.length == 0 ? null : new HashSet(Arrays.asList(names)); synchronized (instances) { for (Iterator> it = instances.entrySet().iterator(); it.hasNext();) { Entry entry = it.next(); if (nameSet != null && !nameSet.contains(entry.getValue())) { continue; } refs.add(new WeakReference(entry.getKey())); txts.add(entry.getValue()); it.remove(); count++; } } if (count == 0) { fail("No instance of this type reported"); } for (int i = 0; i < count; i++) { Reference r = refs.get(i); try { NbTestCase.assertGC(msg + " " + txts.get(i), r); } catch (AssertionFailedError ex) { if (t == null) { t = ex; } else { Throwable last = t; while (last.getCause() != null) { last = last.getCause(); } last.initCause(ex); } } } if (t != null) { throw t; } } } // end of InstancesHandler private static class IL implements Runnable { private static ThreadLocal INTERNAL_LOG = new ThreadLocal(); private final Boolean prev; public IL(boolean on) { prev = INTERNAL_LOG.get(); INTERNAL_LOG.set(on); } @Override public void run() { INTERNAL_LOG.set(prev); } public static boolean isInternalLog() { return Boolean.TRUE.equals(INTERNAL_LOG.get()); } } // end of IL }