001 /*
002 * $Id: SmokeFilter.java,v 1.3 2012/04/20 18:57:54 oboehm Exp $
003 *
004 * Copyright (c) 2010 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 03.05.2010 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.junit.internal;
022
023 import java.lang.annotation.Annotation;
024 import java.util.*;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.junit.runner.Description;
028 import org.junit.runner.manipulation.Filter;
029 import org.slf4j.*;
030
031 import patterntesting.annotation.check.runtime.MayReturnNull;
032 import patterntesting.runtime.NullConstants;
033 import patterntesting.runtime.annotation.*;
034 import patterntesting.runtime.util.*;
035
036 /**
037 * This filter handles the different annotations like Broken, RunTestOn,
038 * SkipTestOn, IntegrationTest or SmokeTest.
039 *
040 * @author oliver
041 * @since 1.0 (03.05.2010)
042 */
043 public final class SmokeFilter extends Filter {
044
045 private static final Logger log = LoggerFactory.getLogger(SmokeFilter.class);
046 private Date today = new Date();
047 private String osName = Environment.OS_NAME;
048 private String userName = Environment.USER_NAME;
049 private int filteredOut = 0;
050 private final Dictionary<Description, Boolean> shouldRunCache = new Hashtable<Description, Boolean>();
051
052 /**
053 * For testing (e.g. to check the past) you can set the today's date.
054 *
055 * @param date the new today's date
056 */
057 public void setToday(final Date date) {
058 this.today = new Date(date.getTime());
059 }
060
061 /**
062 * For testing you can set the OS name.
063 *
064 * @param name the new os name
065 */
066 public void setOsName(final String name) {
067 this.osName = name;
068 }
069
070 /**
071 * For testing you can set the user name.
072 *
073 * @param name the new user name
074 */
075 public void setUserName(final String name) {
076 this.userName = name;
077 }
078
079 /**
080 * Describes the kind of test e.g. "IntegrationTest".
081 *
082 * @return e.g. "all tests including integration tests"
083 * @see org.junit.runner.manipulation.Filter#describe()
084 */
085 @Override
086 public String describe() {
087 if (Environment.isPropertyEnabled(Environment.INTEGRATION_TEST)) {
088 return "all tests including integration tests";
089 }
090 if (Environment.isPropertyEnabled(Environment.RUN_SMOKE_TESTS)) {
091 return "tests marked as @SmokeTest";
092 }
093 return "all tests (except tests marked as @Broken)";
094 }
095
096 /**
097 * Handles the different annotations like Broken, RunTestOn, SkipTestOn,
098 * IntegrationTest or SmokeTest.
099 * We use a cache (shouldRunCache) here in this implementation to avoid to
100 * much logging of "...run method / skip method..." messages.
101 *
102 * @param description containing the JUnit class and test method
103 * @return depends on the annotation
104 * @see org.junit.runner.manipulation.Filter#shouldRun(org.junit.runner.Description)
105 */
106 @Override
107 public boolean shouldRun(final Description description) {
108 Boolean value = shouldRunCache.get(description);
109 if (value != null) {
110 return value;
111 }
112 try {
113 if (Environment.smokeTestEnabled) {
114 value = isSmokeTest(description);
115 } else if (Environment.integrationTestEnabled) {
116 value = true;
117 } else {
118 value = !isIntegrationTest(description);
119 }
120 shouldRunCache.put(description, value);
121 if (!value) {
122 filteredOut++;
123 }
124 return value;
125 } catch (IllegalArgumentException iae) {
126 log.warn(description + " has incomplete Annotation(s) - " + iae);
127 return false;
128 }
129 }
130
131 /**
132 * Should be ignored.
133 *
134 * @param description the description
135 * @return true, if successful
136 */
137 public boolean shouldBeIgnored(final Description description) {
138 if (Environment.smokeTestEnabled) {
139 return !isSmokeTest(description);
140 }
141 if (isSkipTestOn(description) || !isRunTestOn(description) || isBroken(description)) {
142 return true;
143 }
144 if (!Environment.integrationTestEnabled) {
145 return isIntegrationTest(description);
146 }
147 return false;
148 }
149
150 private boolean isIntegrationTest(final Description description) {
151 IntegrationTest it = getAnnotation(description, IntegrationTest.class);
152 if (it != null) {
153 if (log.isInfoEnabled()) {
154 log.info(description.getDisplayName() + " SKIPPED because "
155 + it.value());
156 }
157 return true;
158 }
159 return false;
160 }
161
162 private boolean isSmokeTest(final Description description) {
163 SmokeTest smokeTest = description.getAnnotation(SmokeTest.class);
164 if (smokeTest != null) {
165 if (log.isInfoEnabled()) {
166 log.info("run " + description.getDisplayName() + " because "
167 + smokeTest.value());
168 }
169 return true;
170 }
171 return false;
172 }
173
174 /**
175 * Returns true if the given method has the RunTestOn annotation and
176 * the condition of it is true.
177 *
178 * @param description with the FrameworkMethod
179 * @return depends on the condition
180 */
181 private static boolean isRunTestOn(final Description description) {
182 RunTestOn runOn = getAnnotation(description, RunTestOn.class);
183 if (runOn == null) {
184 return true;
185 }
186 TestOn testOn = new TestOn();
187 if (testOn.matches(runOn.value(), runOn.osName(), runOn.osArch(), runOn
188 .osVersion(), runOn.host(), runOn.javaVersion(), runOn
189 .javaVendor(), runOn.user(), runOn.property())) {
190 log.info(description + " executed " + testOn.getReason());
191 return true;
192 }
193 return false;
194 }
195
196 /**
197 * Returns true if the given method has the SkipTestOn annotation and
198 * the condition of it is true.
199 *
200 * @param description with the FrameworkMethod
201 * @return depends on the condition
202 */
203 private static boolean isSkipTestOn(final Description description) {
204 SkipTestOn skipOn = getAnnotation(description, SkipTestOn.class);
205 if (skipOn == null) {
206 return false;
207 }
208 TestOn testOn = new TestOn();
209 if (testOn.matches(skipOn.value(), skipOn.osName(), skipOn.osArch(), skipOn
210 .osVersion(), skipOn.host(), skipOn.javaVersion(), skipOn
211 .javaVendor(), skipOn.user(), skipOn.property())) {
212 log.info(description + " SKIPPED because " + testOn.getReason());
213 return true;
214 }
215 return false;
216 }
217
218 private boolean isBroken(final Description description) {
219 Broken broken = getAnnotation(description, Broken.class);
220 if (broken == null) {
221 return false;
222 }
223 return isBroken(DescriptionUtils.getMethodNameOf(description), broken);
224 }
225
226 @MayReturnNull
227 private static <T extends Annotation> T getAnnotation(final Description description,
228 final Class<T> annotationType) {
229 T annotation = description.getAnnotation(annotationType);
230 if (annotation == null) {
231 annotation = DescriptionUtils.getTestClassOf(description).getAnnotation(annotationType);
232 }
233 return annotation;
234 }
235
236 /**
237 * Checks for a given method if the condition of the given Broken
238 * annotations is fulfilled.
239 * <br/>
240 * NOTE: The method has default visibility
241 *
242 * @param method the method
243 * @param broken the broken
244 * @return true, if is broken
245 */
246 @SuppressWarnings("deprecation")
247 public boolean isBroken(final String method, final Broken broken) {
248 String why = broken.why();
249 if (StringUtils.isEmpty(why)) {
250 why = broken.value();
251 }
252 Date till = Converter.toDate(broken.till());
253 if ((till != NullConstants.NULL_DATE) && till.after(today)) {
254 log.info(method + "() SKIPPED because " + broken.value()
255 + " till " + broken.till());
256 return true;
257 }
258 if (StringUtils.isNotEmpty(broken.os()[0])) {
259 log.warn("@Broken(os=...) is deprecated - use @Broken(osName=...)");
260 if (StringHelper.containsIgnoreCase(broken.os(), osName)) {
261 log.info(method + "() SKIPPED because " + why + " on "
262 + Converter.toString(broken.os()));
263 return true;
264 } else {
265 log.debug(method + "() started on " + osName + ", because " + why + " only on "
266 + Converter.toString(broken.os()));
267 return false;
268 }
269 }
270 TestOn testOn = new TestOn();
271 testOn.setOsName(this.osName);
272 testOn.setUserName(this.userName);
273 try {
274 if (testOn.matches(broken.osName(), broken.osArch(), broken
275 .osVersion(), broken.host(), broken.javaVersion(), broken
276 .javaVendor(), broken.user(), broken.property())) {
277 log.info(method + " SKIPPED because " + testOn.getReason());
278 return true;
279 }
280 } catch (IllegalArgumentException canhappen) {
281 log.trace("{}", canhappen);
282 if (till == NullConstants.NULL_DATE) {
283 log.info(method + "() SKIPPED because " + why);
284 return true;
285 } else {
286 log.debug(method + "() started, because it should be fixed since "
287 + broken.till());
288 return false;
289 }
290 }
291 return false;
292 }
293
294 /**
295 * Checks if there are some methods filtered out.
296 * @return true if some methods were filtered
297 */
298 public boolean hasFiltered() {
299 return this.filteredOut > 0;
300 }
301
302 /**
303 * Gets the number of methods which was filtered out.
304 *
305 * @return the filtered number
306 */
307 public int getFilteredNumber() {
308 return this.filteredOut;
309 }
310
311 /**
312 * Prints the description of this filter.
313 *
314 * @return a String including the result of the describe() method
315 * @see java.lang.Object#toString()
316 */
317 @Override
318 public String toString() {
319 return "filter for " + this.describe();
320 }
321
322 }
323