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.Rule;
034import org.junit.rules.MethodRule;
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 {
041    @Inject
042    private RunNotifier runNotifier;
043
044    @Inject
045    private FeaturesRunner runner;
046
047    public static class Feature extends SimpleFeature {
048        protected static final ConditionalIgnoreRule rule = new ConditionalIgnoreRule();
049
050        @Rule
051        public MethodRule methodRule() {
052            return rule;
053        }
054
055    }
056
057    @Retention(RetentionPolicy.RUNTIME)
058    @Target({ ElementType.TYPE, ElementType.METHOD })
059    public @interface Ignore {
060        Class<? extends Condition> condition();
061
062        /**
063         * Optional reason why the test is ignored, reported additionally to the condition class simple name.
064         */
065        String cause() default "";
066    }
067
068    public interface Condition {
069        boolean shouldIgnore();
070    }
071
072    public static final class NXP10926H2Upgrade implements Condition {
073        @Override
074        public boolean shouldIgnore() {
075            return false;
076        }
077    }
078
079    public static final class IgnoreIsolated implements Condition {
080        boolean isIsolated = "org.nuxeo.runtime.testsuite.IsolatedClassloader"
081                .equals(getClass().getClassLoader().getClass().getName());
082
083        @Override
084        public boolean shouldIgnore() {
085            return isIsolated;
086        }
087    }
088
089    public static final class IgnoreLongRunning implements Condition {
090        @Override
091        public boolean shouldIgnore() {
092            return true;
093        }
094    }
095
096    public static final class IgnoreWindows implements Condition {
097        @Override
098        public boolean shouldIgnore() {
099            return SystemUtils.IS_OS_WINDOWS;
100        }
101    }
102
103    @Override
104    public Statement apply(Statement base, FrameworkMethod method, Object fixtureTarget) {
105        Class<?> fixtureType = fixtureTarget.getClass();
106        Method fixtureMethod = method.getMethod();
107        Description description = Description.createTestDescription(fixtureType, fixtureMethod.getName(),
108                fixtureMethod.getAnnotations());
109        return shouldIgnore(base, description, runner.getConfig(method, Ignore.class), fixtureType, fixtureMethod,
110                fixtureTarget);
111    }
112
113    protected Statement shouldIgnore(Statement base, Description description, Ignore ignore, Class<?> type) {
114        return shouldIgnore(base, description, ignore, type, null, null);
115    }
116
117    protected Statement shouldIgnore(Statement base, Description description, Ignore ignore, Class<?> type,
118            Method method, Object target) {
119        Class<? extends Condition> conditionType = ignore.condition();
120        if (conditionType == null) {
121            return base;
122        }
123        if (!newCondition(type, method, target, conditionType).shouldIgnore()) {
124            return base;
125        }
126        return new Statement() {
127
128            @Override
129            public void evaluate() throws Throwable {
130                runNotifier.fireTestIgnored(description);
131            }
132        };
133    }
134
135    protected Condition newCondition(Class<?> type, Method method, Object target,
136            Class<? extends Condition> conditionType) throws Error {
137        Condition condition;
138        try {
139            condition = conditionType.newInstance();
140        } catch (InstantiationException | IllegalAccessException cause) {
141            throw new Error("Cannot instantiate condition of type " + conditionType, cause);
142        }
143        injectCondition(type, method, target, condition);
144        return condition;
145    }
146
147    protected void injectCondition(Class<?> type, Method method, Object target, Condition condition)
148            throws SecurityException, Error {
149        Error errors = new Error("Cannot inject condition parameters in " + condition.getClass());
150        for (Field eachField : condition.getClass().getDeclaredFields()) {
151            if (!eachField.isAnnotationPresent(Inject.class)) {
152                continue;
153            }
154            Object eachValue = null;
155            if (eachField.isAnnotationPresent(Named.class)) {
156                String name = eachField.getAnnotation(Named.class).value();
157                if ("type".equals(name)) {
158                    eachValue = type;
159                } else if ("target".equals(name)) {
160                    eachValue = target;
161                } else if ("method".equals(name)) {
162                    eachValue = method;
163                }
164            } else {
165                Class<?> eachType = eachField.getType();
166                if (eachType.equals(Class.class)) {
167                    eachValue = type;
168                } else if (eachType.equals(Object.class)) {
169                    eachValue = target;
170                } else if (eachType.equals(Method.class)) {
171                    eachValue = method;
172                }
173            }
174            if (eachValue == null) {
175                continue;
176            }
177            eachField.setAccessible(true);
178            try {
179                eachField.set(condition, eachValue);
180            } catch (IllegalArgumentException | IllegalAccessException cause) {
181                errors.addSuppressed(new Error("Cannot inject " + eachField.getName(), cause));
182            }
183        }
184        if (errors.getSuppressed().length > 0) {
185            throw errors;
186        }
187        runner.getInjector().injectMembers(condition);
188    }
189
190}