| /* |
| * Copyright (c) 2010 Artem Tikhomirov 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: |
| * Artem Tikhomirov (independent) - initial API and implementation |
| */ |
| package org.eclipse.gmf.tests; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.junit.Assert; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| /** |
| * The idea is to minimize number of static fields throughout tests; |
| * to keep tests configuration externally to tests themselves; |
| * to allow reuse of single setup instance in partial rerun of few tests |
| * @author artem |
| */ |
| public class Configurator { |
| |
| private final Map<Class<? extends TestCase>, Class<? extends TestConfiguration>> config; |
| private final Map<Class<? extends TestConfiguration>, TestConfiguration> state; |
| // tests from single TestCase usually run sequentially, and never mix with other |
| // TestCases, hence enough to cache only one class/set of methods |
| private Class<?> lastSetupLookupTarget; |
| private Method[] lastSetupLookupResult; |
| |
| |
| public Configurator() { |
| config = new HashMap<Class<? extends TestCase>, Class<? extends TestConfiguration>>(); |
| state = new HashMap<Class<? extends TestConfiguration>, TestConfiguration>(); |
| } |
| |
| public void dispose() { |
| config.clear(); |
| state.clear(); |
| } |
| |
| // configuration instance would get created on demand |
| public void register(Class<? extends TestCase> target, Class<? extends TestConfiguration> configClass) { |
| config.put(target, configClass); |
| } |
| |
| // supplied configuration instance would be used. useful when instance initialization takes extra steps |
| // and/or should be done in advance (i.e. before running any test) |
| public void register(Class<? extends TestCase> target, TestConfiguration configInstance) { |
| Class<? extends TestConfiguration> configClass = configInstance.getClass(); |
| register(target, configClass); |
| registerInstance(configClass, configInstance); |
| } |
| |
| // |
| public void registerInstance(Class<? extends TestConfiguration> configClass, TestConfiguration configInstance) { |
| state.put(configClass, configInstance); |
| } |
| |
| // potentially may provide different configuration per separate test, although we don't use this now |
| private TestConfiguration getConfig(Class<? extends TestCase> target, String testName, boolean instantiate) { |
| Class<? extends TestConfiguration> configClass = config.get(target); |
| if (configClass == null) { |
| return null; |
| } |
| TestConfiguration configInstance = state.get(configClass); |
| if (configInstance == null && instantiate) { |
| configInstance = newInstance(configClass); |
| state.put(configClass, configInstance); // may be null |
| } |
| return configInstance; |
| } |
| |
| private TestConfiguration newInstance(Class<? extends TestConfiguration> configClass) { |
| // in fact, getMethods may lead to troubles if we have subclasses of TestConfiguration with |
| // few newInstance methods, and we pick the one from superclass first. To fix, need to iterate over |
| // subclasses manually, but too lazy now. |
| for (Method m : configClass.getMethods()) { |
| if (Modifier.isStatic(m.getModifiers()) && m.getAnnotation(TestConfiguration.FactoryMethod.class) != null) { |
| if (configClass.isAssignableFrom(m.getReturnType())) { |
| try { |
| // null is ok, client's troubles then |
| return (TestConfiguration) m.invoke(null); |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| // FALL-THROUGH - fail |
| } |
| } |
| Assert.fail("Bad factory method: " + m); |
| assert false; // should not ever get here |
| } |
| } |
| Assert.fail("Can't find factory method in " + configClass); |
| return null; |
| } |
| |
| // instance method just to allow caching |
| private Method[] getSetupMethods(Class<?> target) { |
| if (lastSetupLookupTarget == target) { |
| return lastSetupLookupResult; |
| } |
| |
| lastSetupLookupTarget = target; |
| lastSetupLookupResult = findSetupMethods(target); |
| return lastSetupLookupResult; |
| } |
| |
| private static Method[] findSetupMethods(Class<?> target) { |
| ArrayList<Method> setupMethods = new ArrayList<Method>(3); |
| for (Method m : target.getMethods()) { |
| // may use annotation arguments to denote different configure methods (e.g. taking more than one argument) |
| if (m.getAnnotation(NeedsSetup.class) != null) { |
| Class<?>[] parameterTypes = m.getParameterTypes(); |
| if (parameterTypes.length == 1 && TestConfiguration.class.isAssignableFrom(parameterTypes[0])) { |
| setupMethods.add(m); |
| } else { |
| Assert.fail("Invalid test configuration method:" + m); |
| } |
| } |
| } |
| return setupMethods.toArray(new Method[setupMethods.size()]); |
| } |
| |
| // calls back to initialize test with appropriate configuration object |
| public void prepare(TestCase test) { |
| Class<? extends TestCase> testClass = test.getClass(); |
| Method[] setupMethods = getSetupMethods(testClass); |
| if (setupMethods.length == 0) { |
| // fail? OTOH, test would fail itself if no expected setup would be supplied. |
| // XXX seems reasonable to fail only in case of programming errors (e.g. incorrectly marked method) |
| // and do not fail if none matching setups/methods found. |
| return; |
| } |
| TestConfiguration config = getConfig(testClass, test.getName(), true); |
| if (config == null) { |
| return; |
| } |
| for (Method m : setupMethods) { |
| // invoke all compatible setup methods, ignore those that not good. |
| if (m.getParameterTypes()[0].isInstance(config)) { |
| try { |
| m.invoke(test, config); |
| } catch (Exception ex) { |
| // wrong configuration method IS programming error |
| Assert.fail(ex.toString()); |
| } |
| } |
| } |
| } |
| |
| public static TestSuite feed(Class<? extends TestCase> theClass, TestConfiguration config, String suiteName) { |
| TestSuite suite = new TestSuite(theClass, suiteName); |
| try { |
| for (Method m : findSetupMethods(theClass)) { |
| if (m.getParameterTypes()[0].isInstance(config)) { |
| for (Enumeration<?> en = suite.tests(); en.hasMoreElements(); ) { |
| Object nextTest = en.nextElement(); |
| m.invoke(nextTest, config); |
| } |
| } |
| } |
| } catch (Exception ex) { |
| suite.addTest(new ConfigurationFailedCase(theClass.getName() + ": " + ex.getMessage(), ex)); |
| } |
| return suite; |
| } |
| } |