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(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
136    protected Condition newCondition(Class<?> type, Method method, Object target,
137            Class<? extends Condition> conditionType) throws Error {
138        Condition condition;
139        try {
140            condition = conditionType.newInstance();
141        } catch (InstantiationException | IllegalAccessException cause) {
142            throw new Error("Cannot instantiate condition of type " + conditionType, cause);
143        }
144        injectCondition(type, method, target, condition);
145        return condition;
146    }
147
148    protected void injectCondition(Class<?> type, Method method, Object target, Condition condition)
149            throws SecurityException, Error {
150        Error errors = new Error("Cannot inject condition parameters in " + condition.getClass());
151        for (Field eachField : condition.getClass().getDeclaredFields()) {
152            if (!eachField.isAnnotationPresent(Inject.class)) {
153                continue;
154            }
155            Object eachValue = null;
156            if (eachField.isAnnotationPresent(Named.class)) {
157                String name = eachField.getAnnotation(Named.class).value();
158                if ("type".equals(name)) {
159                    eachValue = type;
160                } else if ("target".equals(name)) {
161                    eachValue = target;
162                } else if ("method".equals(name)) {
163                    eachValue = method;
164                }
165            } else {
166                Class<?> eachType = eachField.getType();
167                if (eachType.equals(Class.class)) {
168                    eachValue = type;
169                } else if (eachType.equals(Object.class)) {
170                    eachValue = target;
171                } else if (eachType.equals(Method.class)) {
172                    eachValue = method;
173                }
174            }
175            if (eachValue == null) {
176                continue;
177            }
178            eachField.setAccessible(true);
179            try {
180                eachField.set(condition, eachValue);
181            } catch (IllegalArgumentException | IllegalAccessException cause) {
182                errors.addSuppressed(new Error("Cannot inject " + eachField.getName(), cause));
183            }
184        }
185        if (errors.getSuppressed().length > 0) {
186            throw errors;
187        }
188        runner.getInjector().injectMembers(condition);
189    }
190
191}