| /* |
| * 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 test; |
| |
| import java.io.PrintStream; |
| |
| import org.junit.Assert; |
| |
| /** |
| * Helper class to make sure that steps in a test happen in the correct order. Instantiate |
| * this class and subsequently invoke <code>step(nr)</code> with steps starting at 1. You |
| * can also have threads wait until you arrive at a certain step. |
| * |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| public class Ensure { |
| private final boolean DEBUG; |
| private static long INSTANCE = 0; |
| private static final int RESOLUTION = 100; |
| private static PrintStream STREAM = System.out; |
| int step = 0; |
| private Throwable m_throwable; |
| private boolean previousStepFailed; |
| |
| public Ensure() { |
| this(true); |
| } |
| |
| public Ensure(boolean debug) { |
| DEBUG = debug; |
| if (DEBUG) { |
| INSTANCE++; |
| } |
| } |
| |
| public void setStream(PrintStream output) { |
| STREAM = output; |
| } |
| |
| /** |
| * Mark this point as step <code>nr</code>. |
| * |
| * @param nr the step we are in |
| */ |
| public synchronized void step(int nr) { |
| if (previousStepFailed) { |
| throw new RuntimeException("can not enter into step " + nr + " (some previous steps failed)"); |
| } |
| step++; |
| try { |
| Assert.assertEquals(nr, step); |
| } catch (Throwable e) { |
| previousStepFailed = true; |
| throw e; |
| } |
| if (DEBUG) { |
| String info = getLineInfo(3); |
| STREAM.println("[Ensure " + INSTANCE + "] step " + step + " [" + currentThread() + "] " + info); |
| } |
| notifyAll(); |
| } |
| |
| private String getLineInfo(int depth) { |
| StackTraceElement[] trace = Thread.currentThread().getStackTrace(); |
| String info = trace[depth].getClassName() + "." + trace[depth].getMethodName() + ":" + trace[depth].getLineNumber(); |
| return info; |
| } |
| |
| /** |
| * Mark this point as the next step. |
| */ |
| public synchronized void step() { |
| if (previousStepFailed) { |
| throw new RuntimeException("can not enter into step " + (step+1) + " (some previous steps failed)"); |
| } |
| step++; |
| if (DEBUG) { |
| String info = getLineInfo(3); |
| STREAM.println("[Ensure " + INSTANCE + "] next step " + step + " [" + currentThread() + "] " + info); |
| } |
| notifyAll(); |
| } |
| |
| /** |
| * Wait until we arrive at least at step <code>nr</code> in the process, or fail if that |
| * takes more than <code>timeout</code> milliseconds. If you invoke wait on a thread, |
| * you are effectively assuming some other thread will invoke the <code>step(nr)</code> |
| * method. |
| * |
| * @param nr the step to wait for |
| * @param timeout the number of milliseconds to wait |
| */ |
| public synchronized void waitForStep(int nr, int timeout) { |
| final int initialTimeout = timeout; |
| if (DEBUG) { |
| String info = getLineInfo(3); |
| STREAM.println("[Ensure " + INSTANCE + "] waiting for step " + nr + " [" + currentThread() + "] " + info); |
| } |
| while (step < nr && timeout > 0) { |
| try { |
| wait(RESOLUTION); |
| timeout -= RESOLUTION; |
| } |
| catch (InterruptedException e) {} |
| } |
| if (step < nr) { |
| throw new IllegalStateException("Timed out waiting for " + initialTimeout + " ms for step " + nr + ", we are still at step " + step); |
| } |
| if (DEBUG) { |
| String info = getLineInfo(3); |
| STREAM.println("[Ensure " + INSTANCE + "] arrived at step " + nr + " [" + currentThread() + "] " + info); |
| } |
| } |
| |
| private String currentThread() { |
| Thread thread = Thread.currentThread(); |
| return thread.getId() + " " + thread.getName(); |
| } |
| |
| public static Runnable createRunnableStep(final Ensure ensure, final int nr) { |
| return new Runnable() { public void run() { ensure.step(nr); }}; |
| } |
| |
| public synchronized void steps(Steps steps) { |
| steps.next(this); |
| } |
| |
| /** |
| * Helper class for naming a list of step numbers. If used with the steps(Steps) method |
| * you can define at which steps in time this point should be passed. That means you can |
| * check methods that will get invoked multiple times during a test. |
| */ |
| public static class Steps { |
| private final int[] m_steps; |
| private int m_stepIndex; |
| |
| /** |
| * Create a list of steps and initialize the step counter to zero. |
| */ |
| public Steps(int... steps) { |
| m_steps = steps; |
| m_stepIndex = 0; |
| } |
| |
| /** |
| * Ensure we're at the right step. Will throw an index out of bounds exception if we enter this step more often than defined. |
| */ |
| public void next(Ensure ensure) { |
| ensure.step(m_steps[m_stepIndex++]); |
| } |
| } |
| |
| /** |
| * Saves a thrown exception that occurred in a different thread. You can only save one exception |
| * at a time this way. |
| */ |
| public synchronized void throwable(Throwable throwable) { |
| m_throwable = throwable; |
| } |
| |
| /** |
| * Throws a <code>Throwable</code> if one occurred in a different thread and that thread saved it |
| * using the <code>throwable()</code> method. |
| */ |
| public synchronized void ensure() throws Throwable { |
| if (m_throwable != null) { |
| throw m_throwable; |
| } |
| } |
| } |