001/*
002 * (C) Copyright 2014-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Stephane Lacoin, Julien Carsique
018 *
019 */
020package org.nuxeo.runtime.test.runner;
021
022import java.lang.annotation.ElementType;
023import java.lang.annotation.Retention;
024import java.lang.annotation.RetentionPolicy;
025import java.lang.annotation.Target;
026import java.lang.reflect.Field;
027import java.lang.reflect.Method;
028
029import javax.inject.Inject;
030import javax.inject.Named;
031
032import org.apache.commons.lang3.SystemUtils;
033import org.junit.ClassRule;
034import org.junit.Rule;
035import org.junit.rules.MethodRule;
036import org.junit.rules.TestRule;
037import org.junit.runner.Description;
038import org.junit.runner.notification.RunNotifier;
039import org.junit.runners.model.FrameworkMethod;
040import org.junit.runners.model.Statement;
041
042public class ConditionalIgnoreRule implements MethodRule, TestRule {
043    @Inject
044    private RunNotifier runNotifier;
045
046    public static class Feature extends SimpleFeature {
047        protected static final ConditionalIgnoreRule rule = new ConditionalIgnoreRule();
048
049        @ClassRule
050        public static TestRule classRule() {
051            return rule;
052        }
053
054        @Rule
055        public MethodRule methodRule() {
056            return rule;
057        }
058    }
059
060    @Retention(RetentionPolicy.RUNTIME)
061    @Target({ ElementType.TYPE, ElementType.METHOD })
062    public @interface Ignore {
063        Class<? extends Condition> condition();
064
065        /**
066         * Optional reason why the test is ignored, reported additionally to the condition class simple name.
067         */
068        String cause() default "";
069    }
070
071    public interface Condition {
072        boolean shouldIgnore();
073    }
074
075    public static final class NXP10926H2Upgrade implements Condition {
076        @Override
077        public boolean shouldIgnore() {
078            return false;
079        }
080    }
081
082    public static final class IgnoreIsolated implements Condition {
083        boolean isIsolated = "org.nuxeo.runtime.testsuite.IsolatedClassloader".equals(getClass().getClassLoader()
084                                                                                                .getClass()
085                                                                                                .getName());
086
087        @Override
088        public boolean shouldIgnore() {
089            return isIsolated;
090        }
091    }
092
093    public static final class IgnoreLongRunning implements Condition {
094        @Override
095        public boolean shouldIgnore() {
096            return true;
097        }
098    }
099
100    public static final class IgnoreWindows implements Condition {
101        @Override
102        public boolean shouldIgnore() {
103            return SystemUtils.IS_OS_WINDOWS;
104        }
105    }
106
107    @Override
108    public Statement apply(Statement base, FrameworkMethod method, Object fixtureTarget) {
109        Class<?> fixtureType = fixtureTarget.getClass();
110        Method fixtureMethod = method.getMethod();
111        Description description = Description.createTestDescription(fixtureType, fixtureMethod.getName(),
112                fixtureMethod.getAnnotations());
113        if (fixtureType.isAnnotationPresent(Ignore.class)) {
114            return shouldIgnore(base, description, fixtureType.getAnnotation(Ignore.class), fixtureType, fixtureMethod,
115                    fixtureTarget);
116        }
117        if (fixtureMethod.isAnnotationPresent(Ignore.class)) {
118            return shouldIgnore(base, description, fixtureMethod.getAnnotation(Ignore.class), fixtureType,
119                    fixtureMethod, fixtureTarget);
120        }
121        return base;
122    }
123
124    @Override
125    public Statement apply(Statement base, Description description) {
126        Class<?> fixtureType = description.getTestClass();
127        if (fixtureType.isAnnotationPresent(Ignore.class)) {
128            return shouldIgnore(base, description, fixtureType.getAnnotation(Ignore.class), fixtureType);
129        }
130        return base;
131    }
132
133    protected Statement shouldIgnore(Statement base, Description description, Ignore ignore, Class<?> type) {
134        return shouldIgnore(base, description, ignore, type, null, null);
135    }
136
137    protected Statement shouldIgnore(Statement base, Description description, Ignore ignore, Class<?> type,
138            Method method, Object target) {
139        Class<? extends Condition> conditionType = ignore.condition();
140        if (conditionType == null) {
141            return base;
142        }
143        Condition condition = newCondition(type, method, target, conditionType);
144        if (!condition.shouldIgnore()) {
145            return base;
146        }
147        return new Statement() {
148            @Override
149            public void evaluate() throws Throwable {
150                runNotifier.fireTestIgnored(description);
151            }
152        };
153    }
154
155    protected Condition newCondition(Class<?> type, Method method, Object target,
156            Class<? extends Condition> conditionType) throws Error {
157        Condition condition;
158        try {
159            condition = conditionType.newInstance();
160        } catch (InstantiationException | IllegalAccessException cause) {
161            throw new Error("Cannot instantiate condition of type " + conditionType, cause);
162        }
163        injectCondition(type, method, target, condition);
164        return condition;
165    }
166
167    protected void injectCondition(Class<?> type, Method method, Object target, Condition condition)
168            throws SecurityException, Error {
169        Error errors = new Error("Cannot inject condition parameters in " + condition.getClass());
170        for (Field eachField : condition.getClass().getDeclaredFields()) {
171            if (!eachField.isAnnotationPresent(Inject.class)) {
172                continue;
173            }
174            Object eachValue = null;
175            if (eachField.isAnnotationPresent(Named.class)) {
176                String name = eachField.getAnnotation(Named.class).value();
177                if ("type".equals(name)) {
178                    eachValue = type;
179                } else if ("target".equals(name)) {
180                    eachValue = target;
181                } else if ("method".equals(name)) {
182                    eachValue = method;
183                }
184            } else {
185                Class<?> eachType = eachField.getType();
186                if (eachType.equals(Class.class)) {
187                    eachValue = type;
188                } else if (eachType.equals(Object.class)) {
189                    eachValue = target;
190                } else if (eachType.equals(Method.class)) {
191                    eachValue = method;
192                }
193            }
194            if (eachValue == null) {
195                errors.addSuppressed(new Error("Cannot inject " + eachField.getName()));
196            }
197            eachField.setAccessible(true);
198            try {
199                eachField.set(condition, eachValue);
200            } catch (IllegalArgumentException | IllegalAccessException cause) {
201                errors.addSuppressed(new Error("Cannot inject " + eachField.getName(), cause));
202            }
203        }
204        if (errors.getSuppressed().length > 0) {
205            throw errors;
206        }
207    }
208
209}