blob: 4d2378f12a5fb70cc900632d6485e3bdb0ca2cb6 [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package test;
20
21import java.io.PrintStream;
22
23import org.junit.Assert;
24
25/**
26 * Helper class to make sure that steps in a test happen in the correct order. Instantiate
27 * this class and subsequently invoke <code>step(nr)</code> with steps starting at 1. You
28 * can also have threads wait until you arrive at a certain step.
29 *
30 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
31 */
32public class Ensure {
33 private final boolean DEBUG;
34 private static long INSTANCE = 0;
35 private static final int RESOLUTION = 100;
36 private static PrintStream STREAM = System.out;
37 int step = 0;
38 private Throwable m_throwable;
39 private boolean previousStepFailed;
40
41 public Ensure() {
42 this(true);
43 }
44
45 public Ensure(boolean debug) {
46 DEBUG = debug;
47 if (DEBUG) {
48 INSTANCE++;
49 }
50 }
51
52 public void setStream(PrintStream output) {
53 STREAM = output;
54 }
55
56 /**
57 * Mark this point as step <code>nr</code>.
58 *
59 * @param nr the step we are in
60 */
61 public synchronized void step(int nr) {
62 if (previousStepFailed) {
63 throw new RuntimeException("can not enter into step " + nr + " (some previous steps failed)");
64 }
65 step++;
66 try {
67 Assert.assertEquals(nr, step);
68 } catch (Throwable e) {
69 previousStepFailed = true;
70 throw e;
71 }
72 if (DEBUG) {
73 String info = getLineInfo(3);
74 STREAM.println("[Ensure " + INSTANCE + "] step " + step + " [" + currentThread() + "] " + info);
75 }
76 notifyAll();
77 }
78
79 private String getLineInfo(int depth) {
80 StackTraceElement[] trace = Thread.currentThread().getStackTrace();
81 String info = trace[depth].getClassName() + "." + trace[depth].getMethodName() + ":" + trace[depth].getLineNumber();
82 return info;
83 }
84
85 /**
86 * Mark this point as the next step.
87 */
88 public synchronized void step() {
89 if (previousStepFailed) {
90 throw new RuntimeException("can not enter into step " + (step+1) + " (some previous steps failed)");
91 }
92 step++;
93 if (DEBUG) {
94 String info = getLineInfo(3);
95 STREAM.println("[Ensure " + INSTANCE + "] next step " + step + " [" + currentThread() + "] " + info);
96 }
97 notifyAll();
98 }
99
100 /**
101 * Wait until we arrive at least at step <code>nr</code> in the process, or fail if that
102 * takes more than <code>timeout</code> milliseconds. If you invoke wait on a thread,
103 * you are effectively assuming some other thread will invoke the <code>step(nr)</code>
104 * method.
105 *
106 * @param nr the step to wait for
107 * @param timeout the number of milliseconds to wait
108 */
109 public synchronized void waitForStep(int nr, int timeout) {
110 final int initialTimeout = timeout;
111 if (DEBUG) {
112 String info = getLineInfo(3);
113 STREAM.println("[Ensure " + INSTANCE + "] waiting for step " + nr + " [" + currentThread() + "] " + info);
114 }
115 while (step < nr && timeout > 0) {
116 try {
117 wait(RESOLUTION);
118 timeout -= RESOLUTION;
119 }
120 catch (InterruptedException e) {}
121 }
122 if (step < nr) {
123 throw new IllegalStateException("Timed out waiting for " + initialTimeout + " ms for step " + nr + ", we are still at step " + step);
124 }
125 if (DEBUG) {
126 String info = getLineInfo(3);
127 STREAM.println("[Ensure " + INSTANCE + "] arrived at step " + nr + " [" + currentThread() + "] " + info);
128 }
129 }
130
131 private String currentThread() {
132 Thread thread = Thread.currentThread();
133 return thread.getId() + " " + thread.getName();
134 }
135
136 public static Runnable createRunnableStep(final Ensure ensure, final int nr) {
137 return new Runnable() { public void run() { ensure.step(nr); }};
138 }
139
140 public synchronized void steps(Steps steps) {
141 steps.next(this);
142 }
143
144 /**
145 * Helper class for naming a list of step numbers. If used with the steps(Steps) method
146 * you can define at which steps in time this point should be passed. That means you can
147 * check methods that will get invoked multiple times during a test.
148 */
149 public static class Steps {
150 private final int[] m_steps;
151 private int m_stepIndex;
152
153 /**
154 * Create a list of steps and initialize the step counter to zero.
155 */
156 public Steps(int... steps) {
157 m_steps = steps;
158 m_stepIndex = 0;
159 }
160
161 /**
162 * Ensure we're at the right step. Will throw an index out of bounds exception if we enter this step more often than defined.
163 */
164 public void next(Ensure ensure) {
165 ensure.step(m_steps[m_stepIndex++]);
166 }
167 }
168
169 /**
170 * Saves a thrown exception that occurred in a different thread. You can only save one exception
171 * at a time this way.
172 */
173 public synchronized void throwable(Throwable throwable) {
174 m_throwable = throwable;
175 }
176
177 /**
178 * Throws a <code>Throwable</code> if one occurred in a different thread and that thread saved it
179 * using the <code>throwable()</code> method.
180 */
181 public synchronized void ensure() throws Throwable {
182 if (m_throwable != null) {
183 throw m_throwable;
184 }
185 }
186}