502516: junit4 add ConditionalIgnoreRule to Junit4TestFixtureRunner 

Change-Id: I7efbbaf28f72dd71a3ece1e87dfb4baa92eb98e7
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=502516
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/ConditionalIgnoreRule.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/ConditionalIgnoreRule.java
new file mode 100644
index 0000000..11d2409
--- /dev/null
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/ConditionalIgnoreRule.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Frank Becker and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Frank Becker and others - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mylyn.commons.sdk.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Modifier;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import com.google.common.base.Throwables;
+
+/**
+ * Cobbled together from: http://www.codeaffine.com/2013/11/18/a-junit-rule-to-conditionally-ignore-tests/
+ * https://gist.github.com/yinzara/9980184 http://cwd.dhemery.com/2010/12/junit-rules/
+ * (http://stackoverflow.com/questions/28145735/androidjunit4-class-org-junit-assume-assumetrue-assumptionviolatedexception/
+ */
+
+public class ConditionalIgnoreRule implements TestRule {
+
+	private final IFixtureJUnitClass fixtureJUnitClass;
+
+	public ConditionalIgnoreRule(IFixtureJUnitClass fixtureJUnitClass) {
+		super();
+		this.fixtureJUnitClass = fixtureJUnitClass;
+	}
+
+	public interface IgnoreCondition {
+		boolean isSatisfied(AbstractTestFixture fixture);
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@Target({ ElementType.METHOD })
+	public @interface ConditionalIgnore {
+		Class<? extends IgnoreCondition> condition();
+	}
+
+	@Override
+	public Statement apply(Statement aStatement, Description aDescription) {
+		Statement result = aStatement;
+		if (hasConditionalIgnoreAnnotation(aDescription)) {
+			IgnoreCondition condition = getIgnoreCondition(aDescription);
+			if (condition.isSatisfied(fixtureJUnitClass.getActualFixture())) {
+				result = new IgnoreStatement();
+			}
+		}
+
+		return result;
+	}
+
+	private static boolean hasConditionalIgnoreAnnotation(Description aDescription) {
+		return aDescription.getAnnotation(ConditionalIgnore.class) != null;
+	}
+
+	private static IgnoreCondition getIgnoreCondition(Description aDescription) {
+		ConditionalIgnore annotation = aDescription.getAnnotation(ConditionalIgnore.class);
+		return new IgnoreConditionCreator(aDescription.getTestClass(), annotation).create();
+	}
+
+	private static class IgnoreConditionCreator {
+		private final Class<?> testClass;
+
+		private final Class<? extends IgnoreCondition> conditionType;
+
+		IgnoreConditionCreator(Class<?> testClass, ConditionalIgnore annotation) {
+			this.testClass = testClass;
+			this.conditionType = annotation.condition();
+		}
+
+		IgnoreCondition create() {
+			checkConditionType();
+			try {
+				return createCondition();
+			} catch (Exception re) {
+				throw Throwables.propagate(re);
+			}
+		}
+
+		private IgnoreCondition createCondition() throws Exception {
+			IgnoreCondition result;
+			if (isConditionTypeStandalone()) {
+				result = conditionType.newInstance();
+			} else {
+				result = conditionType.getDeclaredConstructor(testClass).newInstance(testClass);
+			}
+			return result;
+		}
+
+		private void checkConditionType() {
+			if (!isConditionTypeStandalone() && !isConditionTypeDeclaredInTarget()) {
+				String msg = "Conditional class '%s' is a member class "
+						+ "but was not declared inside the test case using it.\n"
+						+ "Either make this class a static class, "
+						+ "standalone class (by declaring it in it's own file) "
+						+ "or move it inside the test case using it";
+				throw new IllegalArgumentException(String.format(msg, conditionType.getName()));
+			}
+		}
+
+		private boolean isConditionTypeStandalone() {
+			return !conditionType.isMemberClass() || Modifier.isStatic(conditionType.getModifiers());
+		}
+
+		private boolean isConditionTypeDeclaredInTarget() {
+			return testClass.getClass().isAssignableFrom(conditionType.getDeclaringClass());
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IFixtureJUnitClass.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IFixtureJUnitClass.java
new file mode 100644
index 0000000..706351d
--- /dev/null
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IFixtureJUnitClass.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Frank Becker and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Frank Becker - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.sdk.util;
+
+public interface IFixtureJUnitClass {
+	public AbstractTestFixture getActualFixture();
+}
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreRuleRuntimeException.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreRuleRuntimeException.java
new file mode 100644
index 0000000..ebf32e9
--- /dev/null
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreRuleRuntimeException.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Frank Becker and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Frank Becker - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.sdk.util;
+
+public class IgnoreRuleRuntimeException extends RuntimeException {
+
+}
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreStatement.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreStatement.java
new file mode 100644
index 0000000..97d1b14
--- /dev/null
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/IgnoreStatement.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Frank Becker and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Frank Becker - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.sdk.util;
+
+import org.junit.runners.model.Statement;
+
+public class IgnoreStatement extends Statement {
+
+	@Override
+	public void evaluate() {
+		throw new IgnoreRuleRuntimeException();
+	}
+
+}
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/Junit4TestFixtureRunner.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/Junit4TestFixtureRunner.java
index 9794414..b4acff7 100644
--- a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/Junit4TestFixtureRunner.java
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/Junit4TestFixtureRunner.java
@@ -20,6 +20,15 @@
 import java.util.Collections;
 import java.util.List;
 
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.mylyn.commons.core.StatusHandler;
+import org.eclipse.osgi.util.NLS;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.internal.runners.model.EachTestNotifier;
+import org.junit.internal.runners.model.ReflectiveCallable;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
 import org.junit.runner.Runner;
 import org.junit.runner.notification.RunNotifier;
 import org.junit.runners.BlockJUnit4ClassRunner;
@@ -86,6 +95,64 @@
 		protected Statement classBlock(RunNotifier notifier) {
 			return childrenInvoker(notifier);
 		}
+
+		@Override
+		protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
+			Description description = describeChild(method);
+			if (isIgnored(method)) {
+				notifier.fireTestIgnored(description);
+			} else {
+				Object test = null;
+				try {
+					test = new ReflectiveCallable() {
+						@Override
+						protected Object runReflectiveCall() throws Throwable {
+							return createTest();
+						}
+					}.run();
+				} catch (Throwable e) {
+					StatusHandler.log(new Status(IStatus.ERROR, "org.eclipse.mylyn.commons.sdk.util", //$NON-NLS-1$
+							NLS.bind("TestClassRunnerForFixture: Testclass {0} has no public constructor", //$NON-NLS-1$
+									getTestClass().getName()),
+							e));
+					return;
+				}
+				boolean skipped = false;
+				if (test != null) {
+					List<TestRule> testRules = getTestRules(test);
+					for (TestRule testRule : testRules) {
+						if (testRule instanceof ConditionalIgnoreRule) {
+							Statement statement = testRule.apply(null, description);
+							if (statement instanceof IgnoreStatement) {
+								skipped = true;
+								break;
+							}
+						}
+					}
+					if (skipped) {
+						notifier.fireTestIgnored(description);
+					} else {
+						runTest(methodBlock(method), description, notifier);
+					}
+				}
+			}
+		}
+
+		protected final void runTest(Statement statement, Description description, RunNotifier notifier) {
+			EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
+			eachNotifier.fireTestStarted();
+			try {
+				statement.evaluate();
+			} catch (AssumptionViolatedException e) {
+				eachNotifier.addFailedAssumption(e);
+			} catch (IgnoreRuleRuntimeException e) {
+				eachNotifier.fireTestIgnored();
+			} catch (Throwable e) {
+				eachNotifier.addFailure(e);
+			} finally {
+				eachNotifier.fireTestFinished();
+			}
+		}
 	}
 
 	private final ArrayList<Runner> runners = new ArrayList<Runner>();
@@ -106,22 +173,21 @@
 
 		String fixtureType = null;
 		for (Annotation annotation : getTestClass().getAnnotations()) {
-			if ("org.eclipse.mylyn.commons.sdk.util.Junit4TestFixtureRunner.OnlyRunWithProperty"
-					.equals(annotation.annotationType().getCanonicalName())) {
+			if (annotation.annotationType() == RunOnlyWhenProperty.class) {
 				RunOnlyWhenProperty onlyWhenProperty = (RunOnlyWhenProperty) annotation;
 				restrictProperty = onlyWhenProperty.property();
 				restrictValue = onlyWhenProperty.value();
 			}
-			if ("org.eclipse.mylyn.commons.sdk.util.Junit4TestFixtureRunner.FixtureDefinition"
-					.equals(annotation.annotationType().getCanonicalName())) {
+			if (annotation.annotationType() == FixtureDefinition.class) {
 				FixtureDefinition fixtueDef = (FixtureDefinition) annotation;
 				fixtureClass = fixtueDef.fixtureClass();
 				fixtureType = fixtueDef.fixtureType();
 			}
 		}
 		if (fixtureType != null) {
-			List<AbstractTestFixture> parametersList = (List<AbstractTestFixture>) TestConfiguration.getDefault()
-					.discover(fixtureClass, fixtureType);
+			TestConfiguration defFixture = TestConfiguration.getDefault();
+			List<AbstractTestFixture> parametersList = (List<AbstractTestFixture>) defFixture.discover(fixtureClass,
+					fixtureType);
 			List<AbstractTestFixture> fixturesToExecute = new ArrayList<AbstractTestFixture>();
 			if (restrictProperty != null) {
 				for (AbstractTestFixture abstractFixture : parametersList) {