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