All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.inftec.ju.testing.db.DataSetExportSuite Maven / Gradle / Ivy

There is a newer version: 4.5.1-11
Show newest version
package ch.inftec.ju.testing.db;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.junit.Test;
import org.junit.internal.builders.AnnotatedBuilder;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.inftec.ju.testing.db.DbTestAnnotationHandler.DataSetConfigInfo;
import ch.inftec.ju.testing.db.DbTestAnnotationHandler.DbTestAnnotationInfo;
import ch.inftec.ju.util.JuRuntimeException;
import ch.inftec.ju.util.ReflectUtils;
import ch.inftec.ju.util.XString;

/**
 * Suite class that can be used to execute tests with DataSetExport in the correct order.
 * 
 * @author Martin Meyer 
 * 
 */
public class DataSetExportSuite extends ParentRunner {
	private static Logger logger = LoggerFactory.getLogger(DataSetExportSuite.class);

	private DataSetAwareParentRunner parentRunner;

	// private List runners;

	public DataSetExportSuite(Class klass, RunnerBuilder builder) throws InitializationError {
		super(klass);

		logger.debug("Initializing DataSetExportSuite");

		// Check if we have a RootSuite set
		logger.debug("Looking for RootSuite");
		List rootSuiteAnnos = ReflectUtils.getAnnotations(klass, RootSuite.class, true);
		if (rootSuiteAnnos.size() > 0) {
			try {
				@SuppressWarnings("unchecked")
				Class parentRunnerClass = (Class) rootSuiteAnnos.get(0).value();
				logger.debug("Found RootSuite: " + parentRunnerClass);

				@SuppressWarnings("unchecked")
				ParentRunner suiteRunner = (ParentRunner) new AnnotatedBuilder(builder).runnerForClass(parentRunnerClass);

				this.parentRunner = new DataSetAwareParentRunner<>(suiteRunner);

				// Print dependency report
				XString xs = new XString();
				this.parentRunner.printDependencyReport(xs);
				logger.info("DataSetExport Suite dependency information: \n\n" + xs.toString());

				if (this.parentRunner.hasCyclicDependencies()) {
					throw new JuRuntimeException("Cyclic dependencies detected. Check log for details.");
				}
			} catch (JuRuntimeException ex) {
				throw ex;
			} catch (Exception ex) {
				throw new JuRuntimeException("Couldn't initialize RootSuite", ex);
			}
		} else {
			logger.debug("No RootSuite specified");

			throw new JuRuntimeException("DataSetExportSuite without RootSuite annotation not supported yet");
		}
	}

	private static class DataSetAwareParentRunner extends ParentRunner {
		private final ParentRunner originalRunner;
		private List originalChildren;
		private List reorderedChildren;

		public DataSetAwareParentRunner(ParentRunner originalRunner) throws InitializationError {
			super(originalRunner.getTestClass().getJavaClass());

			this.originalRunner = originalRunner;

			this.reorganizeRunner();
		}

		public boolean hasCyclicDependencies() {
			for (DataSetObject o : this.reorderedChildren) {
				if (o.hasCyclicDependency)
					return true;

				if (o.relatedRunner != null) {
					return o.relatedRunner.hasCyclicDependencies();
				}
			}

			return false;
		}

		private void reorganizeRunner() {
			try {
				Method m = ReflectUtils.getDeclaredMethodInherited(this.originalRunner.getClass(), "getChildren", null);
				m.setAccessible(true);
				@SuppressWarnings("unchecked")
				List children = (List) m.invoke(this.originalRunner, (Object[]) null);
				this.originalChildren = children;

				// Warp all original children in DataSetAwareParentRunners and DataSetObjects
				this.reorderedChildren = new ArrayList<>();
				for (T originalChild : this.originalChildren) {
					// Check if we have a parent runner
					if (originalChild instanceof ParentRunner) {
						DataSetAwareParentRunner actualChild = new DataSetAwareParentRunner<>((ParentRunner) originalChild);
						DataSetObject dataSetObject = new DataSetObject(actualChild, null);
						dataSetObject.gatherDataSetInfos(actualChild.reorderedChildren);
						this.reorderedChildren.add(dataSetObject);
					} else if (originalChild instanceof FrameworkMethod) {
						// Framework Method
						FrameworkMethod frameworkMethod = (FrameworkMethod) originalChild;
						Method testMethod = frameworkMethod.getMethod();
						System.err.println(testMethod);

						DataSetObject dataSetObject = new DataSetObject(null, frameworkMethod);
						dataSetObject.gatherDataSetInfos(testMethod, this.describeChild(originalChild));
						this.reorderedChildren.add(dataSetObject);
					}
				}

				// Now, we can sort the list
				logger.debug("Sorting runner " + this.originalRunner.getDescription().getDisplayName());
				this.sort(this.reorderedChildren);
			} catch (Exception ex) {
				throw new JuRuntimeException("Couldn't reorganize children for " + this.originalRunner.getDescription().getDisplayName(),
						ex);
			}
		}

		private void printDependencyReport(XString xs) {
			xs.addLine("Dependency info for " + this.originalRunner.getDescription().getDisplayName());
			xs.increaseIndent();

			for (DataSetObject o : this.reorderedChildren) {
				// Check if we have dependencies. If so, list them...
				// TODO
				// xs.newLine();
				
				if (o.relatedRunner != null && o.relatedRunner instanceof DataSetAwareParentRunner) {
					xs.newLine();
					((DataSetAwareParentRunner) o.relatedRunner).printDependencyReport(xs);
				} else {
					xs.addLine(o);

					if (o.hasCyclicDependency) {
						xs.addText(" -> ! CYCLIC DEPENDENCIES !");
					}

					xs.increaseIndent();
					// Print explicit dependencies
					if (!o.dataSetImports.isEmpty()) {
						xs.addLine("Imports: ");
						for (String imports : o.dataSetImports) {
							xs.addText(imports);
						}
					}
					if (!o.dataSetExports.isEmpty()) {
						xs.addLine("Exports: ");
						for (String exports : o.dataSetExports) {
							xs.addText(exports);
						}
					}
					xs.decreaseIndent();
				}
			}

			xs.decreaseIndent();
		}

		@Override
		protected List getChildren() {
			List orderedChildrenList = new ArrayList<>();
			for (DataSetObject dataSetObject : this.reorderedChildren) {
				@SuppressWarnings("unchecked")
				T relatedObject = (T) dataSetObject.getRelatedObject();
				orderedChildrenList.add(relatedObject);
			}

			return orderedChildrenList;
		}

		@Override
		protected Description describeChild(T child) {
			try {
				Method m = ReflectUtils.getDeclaredMethodInherited(this.originalRunner.getClass(), "describeChild",
						new Class[] { Object.class });
				m.setAccessible(true);
				return (Description) m.invoke(this.originalRunner, child);
			} catch (Exception ex) {
				throw new JuRuntimeException("Couldn't invoke describeChild of base parent runner", ex);
			}
		}

		@Override
		protected void runChild(T child, RunNotifier notifier) {
			try {
				Method m = ReflectUtils.getDeclaredMethodInherited(this.originalRunner.getClass(), "runChild", new Class[] {
						Object.class, RunNotifier.class });
				m.setAccessible(true);
				m.invoke(this.originalRunner, child, notifier);
			} catch (Exception ex) {
				throw new JuRuntimeException("Couldn't invoke runChild of base parent runner", ex);
			}
		}

		private void sort(List dataSetObjects) {
			List sortedObjects = new ArrayList<>();

			// First, add all objects that contain no DataSet annotations at all
			for (DataSetObject o : dataSetObjects) {
				if (o.dataSetExports.isEmpty() && o.dataSetImports.isEmpty()) {
					sortedObjects.add(o);
				}
			}
			dataSetObjects.removeAll(sortedObjects);

			// Now, iterate through the list and add objects without dependencies on others as long as the list is not empty
			while (!dataSetObjects.isEmpty()) {
				for (DataSetObject o : dataSetObjects) {
					// Check if the object has no imports from other pending objects
					boolean hasDependentImports = false;
					for (DataSetObject otherObject : dataSetObjects) {
						if (otherObject != o) {
							for (String imports : o.dataSetImports) {
								if (otherObject.dataSetExports.contains(imports)) {
									hasDependentImports = true;
									break;
								}
							}
						}
					}

					if (!hasDependentImports) {
						sortedObjects.add(o);
					}
				}

				if (!dataSetObjects.removeAll(sortedObjects)) {
					break;
				}
			}

			// Add cyclic dependency information (if any)
			if (!dataSetObjects.isEmpty()) {
				for (DataSetObject o : dataSetObjects) {
					// For now, we'll just flag objects that suffer from cyclic dependencies. Would be nicer to
					// actually see what exactly they are...
					o.hasCyclicDependency = true;
				}
				sortedObjects.addAll(dataSetObjects);
				dataSetObjects.clear();
			}

			// Re-add the objects in the right order
			dataSetObjects.addAll(sortedObjects);
		}
	}

	private static class DataSetObject {
		private final Set dataSetImports = new LinkedHashSet<>();
		private final Set dataSetExports = new LinkedHashSet<>();
		private final DataSetAwareParentRunner relatedRunner;
		private final FrameworkMethod relatedMethod;

		private boolean hasCyclicDependency = false;

		public DataSetObject(DataSetAwareParentRunner relatedRunner, FrameworkMethod relatedMethod) {
			this.relatedRunner = relatedRunner;
			this.relatedMethod = relatedMethod;
		}

		public Object getRelatedObject() {
			return this.relatedMethod != null ? this.relatedMethod : this.relatedRunner;
		}

		public void gatherDataSetInfos(Method m, Description desc) {
			logger.debug("Gathering DataSetInfos for method " + m);

			DbTestAnnotationInfo annoInfo = DbTestAnnotationHandler.getDbTestAnnotationInfo(m);
			DataSetConfigInfo configInfo = DbTestAnnotationHandler.getDataSetConfigInfo(annoInfo);

			// Imports
			for (ReflectUtils.AnnotationInfo dsImport : annoInfo.getDataSetAnnos()) {
				// Clean insert
				this.dataSetImports.add(this.getResourceString("", dsImport.getAnnotation().value()));

				// Inserts
				for (String insert : dsImport.getAnnotation().inserts()) {
					this.dataSetImports.add(this.getResourceString(configInfo.getResourcePrefix(), insert));
				}
			}

			// Export
			for (ReflectUtils.AnnotationInfo dsExport : annoInfo.getDataSetExportAnnos()) {
				// Export
				this.dataSetExports.add(this.getResourceString(configInfo.getResourcePrefix()
						, new DbTestAnnotationHandler(m, desc).getExportFileName(dsExport.getAnnotation())));
			}
		}

		private String getResourceString(String resourcePrefix, String fileName) {
			return Paths.get(resourcePrefix, fileName).toString();
		}

		public void gatherDataSetInfos(List childObjects) {
			logger.debug("Gathering DataSetInfos for " + this);

			for (DataSetObject dsObject : childObjects) {
				this.dataSetImports.addAll(dsObject.dataSetImports);
				this.dataSetExports.addAll(dsObject.dataSetExports);
			}
		}

		@Override
		public String toString() {
			if (this.relatedRunner != null)
				return this.relatedRunner.getDescription().getDisplayName();
			else if (this.relatedMethod != null)
				return this.relatedMethod.getName();
			else
				return super.toString();
		}
	}

	@Override
	protected List getChildren() {
		return this.parentRunner.getChildren();
	}

	@Override
	protected Description describeChild(Runner child) {
		return child.getDescription();
	}

	@Override
	protected void runChild(Runner child, RunNotifier notifier) {
		child.run(notifier);
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.TYPE)
	public @interface RootSuite {
		public Class value();
	}

	public static class TestClass {
		@Test
		public void test() {
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy