package com.googlecode.gwt.test.csv.runner; import com.google.gwt.event.shared.UmbrellaException; import com.googlecode.gwt.test.csv.CsvMethod; import com.googlecode.gwt.test.csv.GwtCsvTest; import com.googlecode.gwt.test.csv.GwtTestCsvException; import com.googlecode.gwt.test.finder.Node; import com.googlecode.gwt.test.finder.ObjectFinder; import com.googlecode.gwt.test.internal.BrowserSimulatorImpl; import com.googlecode.gwt.test.utils.GwtReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; public class CsvRunner { private static Map<Class<?>, Map<String, Method>> csvMethodsCache = new HashMap<Class<?>, Map<String, Method>>(); private static final Logger logger = LoggerFactory.getLogger(CsvRunner.class); private String extendedLineInfo = null; private final HasCsvTestExecutionHandlers hasCsvMethodInvocationHandlers; private int lineNumber = -1; private final List<ObjectFinder> objectFinders = new ArrayList<ObjectFinder>(); public CsvRunner(HasCsvTestExecutionHandlers hasCsvMethodInvocationHandlers) { this.hasCsvMethodInvocationHandlers = hasCsvMethodInvocationHandlers; } public void addObjectFinder(ObjectFinder objectFinder) { objectFinders.add(objectFinder); } public void executeRow(List<String> row, Object fixture) { if (row.size() == 0) { return; } String methodName = row.get(0).trim(); if (!"".equals(methodName)) { List<String> args = new ArrayList<String>(); args.addAll(row); args.remove(0); executeLine(methodName, args, fixture); } } public String getAssertionErrorMessagePrefix() { return "Error line " + getCurrentExecutedLine() + ": "; } @SuppressWarnings("unchecked") public <T> T getNodeValue(Object o, Node node) { Object current = o; Node currentNode = node; while (currentNode != null) { if (current == null) { return null; } String currentName = currentNode.getLabel(); boolean mapEqIsProcessed = false; logger.trace(getProcessingMessagePrefix() + "Processing " + currentName); boolean ok = false; if (!ok) { Method m = GwtReflectionUtils.getMethod(current.getClass(), currentName); if (m == null) { m = GwtReflectionUtils.getMethod(current.getClass(), "get" + currentName); } if (m != null) { try { if (m.getParameterTypes().length == 0 || currentNode.getParams() != null) { current = invoke(current, m, currentNode.getParams()); ok = true; } else { current = findInList(current, m, currentNode.getMapEq(), currentNode.getMap()); mapEqIsProcessed = true; ok = true; } } catch (Exception e) { logger.error(getAssertionErrorMessagePrefix() + "Execution error", e); fail(getAssertionErrorMessagePrefix() + "Unable to get method result on " + o.getClass().getCanonicalName() + ", method " + m.getName() + ", params " + currentNode.getParams()); } } } if (!ok) { Field f = getField(current, current.getClass(), currentName); if (f != null) { try { current = f.get(current); ok = true; } catch (Exception e) { logger.error(getAssertionErrorMessagePrefix() + "Execution error", e); fail(getAssertionErrorMessagePrefix() + "Unable to get field value on " + o.getClass().getCanonicalName() + ", field " + f.getName() + ", params " + node); } } } if (ok && currentNode.getMap() != null) { if (currentNode.getMapEq() == null) { current = proccessMap(current, currentNode.getMap()); } else { if (!mapEqIsProcessed) { if (current instanceof Iterable<?>) { Iterable<Object> list = (Iterable<Object>) current; current = findInIterable(list, currentNode.getMapEq(), currentNode.getMap(), current, null); } else { fail(getAssertionErrorMessagePrefix() + "Not managed type for iteration " + current.getClass().getCanonicalName()); } } } } if (!ok) { return null; } currentNode = currentNode.getNext(); } return (T) current; } public String getProcessingMessagePrefix() { return "Processing line " + (lineNumber + 1) + (extendedLineInfo == null ? "" : " [" + extendedLineInfo + "]") + ": "; } public int runSheet(List<List<String>> sheet, Object fixture) throws Exception { assertThat(fixture).as("Fixture have to be not null").isNotNull(); boolean execute = false; int lineExecuted = 0; lineNumber = 0; for (List<String> row : sheet) { if (execute) { executeRow(row, fixture); lineExecuted++; } if (row != null && row.size() > 0 && "start".equals(row.get(0))) { execute = true; } lineNumber++; } lineNumber = -1; return lineExecuted; } public void setExtendedLineInfo(String extendedLineInfo) { this.extendedLineInfo = extendedLineInfo; } @Override public String toString() { return "CsvRunner executing line " + getCurrentExecutedLine(); } void executeLine(String methodName, List<String> args, Object fixture) { if (methodName.indexOf("**") == 0) { // commented line return; } List<String> filterArgs = new ArrayList<String>(args); removeEmptyElements(filterArgs); transformArgs(filterArgs); Method m = getCsvMethod(fixture.getClass(), methodName); if (m == null) { fail(getAssertionErrorMessagePrefix() + "@" + CsvMethod.class.getSimpleName() + " ' " + methodName + " ' not found in object " + fixture); } GwtReflectionUtils.makeAccessible(m); List<Object> argList = new ArrayList<Object>(); for (Class<?> clazz : m.getParameterTypes()) { if (filterArgs.size() == 0) { if (clazz.isArray()) { argList.add(new String[0]); } else { fail(getAssertionErrorMessagePrefix() + "Too few args for @" + CsvMethod.class.getSimpleName() + " '" + methodName + "'"); } } else { if (clazz.isArray()) { argList.add(filterArgs.toArray(new String[filterArgs.size()])); filterArgs.clear(); } else { argList.add(filterArgs.get(0)); filterArgs.remove(0); } } } if (filterArgs.size() != 0) { fail(getAssertionErrorMessagePrefix() + "Too many args for " + methodName); } try { BrowserSimulatorImpl.get().fireLoopEnd(); Object[] paramValues = argList.toArray(); invokeWithCallbacks(fixture, m, args, paramValues); } catch (Throwable e) { Throwable cause = getFixtureError(e); if (cause instanceof GwtTestCsvException) { throw (GwtTestCsvException) cause; } else if (cause instanceof AssertionError) { throw (AssertionError) cause; } else { throw new GwtTestCsvException(getAssertionErrorMessagePrefix() + "Error invoking @CsvMethod " + m.toString(), cause); } } } private boolean checkCondition(Object n, Node before, String after) { Object result = getNodeValue(n, before); String s = result == null ? null : result.toString(); return after.equals(s); } private Object findInIterable(Iterable<Object> list, Node before, String after, Object current, Method m) { Object found = null; for (Object n : list) { if (checkCondition(n, before, after)) { if (found != null) { fail("Not unique object with condition " + before + "=" + after); } found = n; } } assertThat(found).as( getAssertionErrorMessagePrefix() + "Not found " + before + "=" + after + " in " + current.getClass().getCanonicalName() + (m != null ? " method " + m.getName() : "")).isNotNull(); return found; } private Object findInList(final Object current, final Method m, Node mapEq, String map) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { if (m.getParameterTypes().length != 1 && m.getParameterTypes()[0] != int.class) { fail("Unable to navigate " + current.getClass().getCanonicalName() + " with method " + m.getName()); } Method countM = GwtReflectionUtils.getMethod(current.getClass(), m.getName() + "Count"); if (countM == null) { fail("Count method not found in " + current.getClass().getCanonicalName() + " method " + m.getName()); } if (countM.getParameterTypes().length > 0) { fail("Too many parameter in count method " + current.getClass().getCanonicalName() + " method " + countM.getName()); } logger.debug("Searching in list, field " + mapEq + ", value " + map); final int count = (Integer) countM.invoke(current); return findInIterable(new Iterable<Object>() { public Iterator<Object> iterator() { return new Iterator<Object>() { int counter = 0; public boolean hasNext() { return counter < count; } public Object next() { try { return m.invoke(current, counter++); } catch (Exception e) { throw new GwtTestCsvException("Iterator exception", e); } } public void remove() { throw new UnsupportedOperationException("Remove not implemented"); } }; } }, mapEq, map, current, m); } private Method getCsvMethod(Class<?> clazz, String methodName) { Map<String, Method> classCsvMethods = csvMethodsCache.get(clazz); if (classCsvMethods == null) { classCsvMethods = new HashMap<String, Method>(); csvMethodsCache.put(clazz, classCsvMethods); Map<Method, CsvMethod> csvMethods = GwtReflectionUtils.getAnnotatedMethod(clazz, CsvMethod.class); for (Map.Entry<Method, CsvMethod> entry : csvMethods.entrySet()) { classCsvMethods.put(getMethodName(entry), entry.getKey()); } } Method res = classCsvMethods.get(methodName); if (res != null) { return res; } else if (clazz == GwtCsvTest.class) { return null; } else { Class<?> superClass = clazz.getSuperclass(); if (superClass == null) { return null; } return getCsvMethod(clazz.getSuperclass(), methodName); } } private String getCurrentExecutedLine() { return lineNumber + 1 + (extendedLineInfo == null ? "" : " [" + extendedLineInfo + "]"); } private Field getField(Object fixture, Class<?> clazz, String fieldName) { for (Field f : clazz.getDeclaredFields()) { if (f.getName().equalsIgnoreCase(fieldName)) { GwtReflectionUtils.makeAccessible(f); return f; } } Class<?> superClazz = clazz.getSuperclass(); if (superClazz != null) { return getField(fixture, superClazz, fieldName); } return null; } private Throwable getFixtureError(Throwable cause) { if (cause instanceof InvocationTargetException && cause.getCause() != null) { cause = cause.getCause(); } if (UmbrellaException.class.isInstance(cause)) { cause = cause.getCause(); } return cause; } private String getMethodName(Entry<Method, CsvMethod> entry) { String methodname = entry.getValue().methodName(); return methodname.equals("") ? entry.getKey().getName() : methodname; } private Object invoke(Object current, Method m, List<String> list) throws IllegalArgumentException, IllegalAccessException { logger.debug("Invoking " + m.getName() + " on " + current.getClass().getCanonicalName() + " with param " + list); GwtReflectionUtils.makeAccessible(m); if (list == null) { if (m.getParameterTypes().length == 0) { try { return m.invoke(current); } catch (InvocationTargetException e) { return null; } } } Object[] tab = new Object[m.getParameterTypes().length]; for (int index = 0; index < m.getParameterTypes().length; index++) { if (m.getParameterTypes()[index] == String.class) { tab[index] = list.get(index); } else if (m.getParameterTypes()[index] == Integer.class || m.getParameterTypes()[index] == int.class) { tab[index] = Integer.parseInt(list.get(index)); } else { fail(getAssertionErrorMessagePrefix() + "Not managed type " + m.getParameterTypes()[index]); } } try { return m.invoke(current, tab); } catch (InvocationTargetException e) { return null; } } private void invokeWithCallbacks(Object fixture, Method csvMethod, List<String> args, Object[] paramValues) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { String[] paramIdentifiers = args.toArray(new String[args.size()]); CsvMethodInvocation invocationData = new CsvMethodInvocation(lineNumber + 1, extendedLineInfo, csvMethod, paramIdentifiers, paramValues); for (CsvTestExecutionHandler handler : hasCsvMethodInvocationHandlers.getCsvTestExecutionHandlers()) { handler.beforeCsvMethodInvocation(invocationData); } csvMethod.invoke(fixture, paramValues); for (CsvTestExecutionHandler handler : hasCsvMethodInvocationHandlers.getCsvTestExecutionHandlers()) { handler.afterCsvMethodInvocation(invocationData); } } private Object proccessMap(Object current, String map) { if (current instanceof Map<?, ?>) { Map<?, ?> m = (Map<?, ?>) current; current = m.get(map); } else if (current instanceof List<?>) { List<?> l = (List<?>) current; current = l.get(Integer.parseInt(map)); } else { fail(getAssertionErrorMessagePrefix() + "Object not a map " + current.getClass().getCanonicalName() + " : " + map); } return current; } private void removeEmptyElements(List<String> list) { List<String> newList = new ArrayList<String>(); for (String s : list) { if (!"".equals(s)) { newList.add(s); } } list.clear(); list.addAll(newList); } private void transformArgs(List<String> list) { List<String> newList = new ArrayList<String>(); for (String s : list) { String out = s; if ("*empty*".equals(s)) { out = ""; } else if ("*null*".equals(s)) { out = null; } newList.add(out); } list.clear(); list.addAll(newList); } }