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

org.etlunit.ClassBroadcasterImpl Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
package org.etlunit;

import org.etlunit.context.VariableContext;
import org.etlunit.context.VariableContextImpl;
import org.etlunit.parser.*;
import org.etlunit.util.NeedsTest;

import java.util.*;

@NeedsTest
public class ClassBroadcasterImpl implements ClassBroadcaster
{
	private final ClassLocator locator;
	private final ClassListener listener;
	private final ClassDirector director;
	private final VariableContext context;
	private final Log log;

	public ClassBroadcasterImpl(ClassLocator plocator, ClassDirector pdirector, ClassListener plistener, Log log)
	{
		this(plocator, pdirector, plistener, log, new VariableContextImpl());
	}

	public ClassBroadcasterImpl(ClassLocator plocator, ClassDirector pdirector, ClassListener plistener, Log log, VariableContext variableContext)
	{
		locator = plocator;
		listener = plistener;
		director = pdirector;
		this.log = log;
		context = variableContext;
	}

	@Override
	public int broadcast(StatusReporter statusReporter)
	{
		return broadcast0(statusReporter, false);
	}

	@Override
	public int broadcast(boolean scan_only)
	{
		return broadcast0(null, scan_only);
	}

	private int broadcast0(StatusReporter statusReporter, boolean scan_only)
	{
		if (statusReporter != null)
		{
			statusReporter.scanStarted();
		}

		int testCount = 0;

		StatusReporter.CompletionStatus classStatuses = new StatusReporter.CompletionStatus();

		director.beginBroadcast();

		while (locator.hasNext())
		{
			ETLTestClass cl = locator.next();
			// we don't care about rejected or deferred results
			if (director.accept(cl) == ClassResponder.response_code.accept)
			{
				// create a subcontext for the class
				VariableContext classScope = context.createNestedScope();

				if (statusReporter != null)
				{
					statusReporter.testClassAccepted(cl);
				}

				List methodsToExecute = new ArrayList();

				List methods = cl.getTestMethods();

				Iterator mit = methods.iterator();

				// get a list of all methods before doing anything else
				while (mit.hasNext())
				{
					ETLTestMethod method = mit.next();

					if (director.accept(method) == ClassResponder.response_code.accept)
					{
						if (statusReporter != null)
						{
							statusReporter.testMethodAccepted(method);
						}

						testCount++;

						methodsToExecute.add(method);
					}
					else
					{
						log.info("Test method not accepted:  " + method.getQualifiedName());
					}
				}

				if (methodsToExecute.size() > 0)
				{
					if (!scan_only)
					{
						try
						{
							listener.begin(cl, classScope);
						}
						catch (TestExecutionError err)
						{
							classStatuses.setError(err);
						}
						catch (TestAssertionFailure fail)
						{
							classStatuses.addFailure(fail);
						}
						catch (TestWarning warn)
						{
							classStatuses.addWarning(warn);
						}

						if (!classStatuses.hasFailures())
						{
							// broadcast the variables
							List varlist = cl.getClassVariables();
							Iterator varit = varlist.iterator();

							while (varit.hasNext())
							{
								ETLTestVariable var = varit.next();
								listener.declare(var, classScope);
							}
						}
					}

					try
					{
						// now broadcast all the before class methods.  Any failures here will break
						// the entire chain.
						List beforeClassMethods = cl.getBeforeClassMethods();
						Iterator bcit = beforeClassMethods.iterator();

						while (bcit.hasNext() && !classStatuses.hasFailures())
						{
							ETLTestMethod bcmethod = bcit.next();

							broadcast(bcmethod, classStatuses, scan_only, classScope);
						}
					}
					catch (TestExecutionError err)
					{
						classStatuses.setError(err);
					}

					mit = methodsToExecute.iterator();
					while (mit.hasNext())
					{
						ETLTestMethod method = mit.next();

						List testAnnotList = method.getAnnotations("@Test");

						// we only care about the first one - all others are bogus.
						// There must be at least one or this method would not be a test method
						ETLTestAnnotation testAnnot = testAnnotList.get(0);

						StatusReporter.CompletionStatus methodStatus = new StatusReporter.CompletionStatus();

						if (statusReporter != null)
						{
							statusReporter.testBeginning(method);
						}

						if (classStatuses.hasFailures())
						{
							if (statusReporter != null)
							{
								statusReporter.testCompleted(method, classStatuses);
							}
						}
						else
						{
							try
							{
								ETLTestValueObject value = testAnnot.getValue();

								if (value != null)
								{
									ETLTestValueObject expect = value.query("expected-error-id");

									if (expect != null)
									{
										if (expect.getValueType() != ETLTestValueObject.value_type.quoted_string)
										{
											throw new TestExecutionError("Invalid expected-error-id - must be a string type",
													TestConstants.ERR_INVALID_TEST_ANNOTATION);
										}

										methodStatus.setExpectedErrorId(expect.getValueAsString());
									}

									expect = value.query("expected-failure-ids");

									if (expect != null)
									{
										if (expect.getValueType() != ETLTestValueObject.value_type.list)
										{
											throw new TestExecutionError("Invalid expected-failure-ids - must be a list type",
													TestConstants.ERR_INVALID_TEST_ANNOTATION);
										}

										methodStatus.addExpectedFailureIds(expect.getValueAsListOfStrings());
									}

									expect = value.query("expected-warning-ids");

									if (expect != null)
									{
										if (expect.getValueType() != ETLTestValueObject.value_type.list)
										{
											throw new TestExecutionError("Invalid expected-warning-ids - must be a list type",
													TestConstants.ERR_INVALID_TEST_ANNOTATION);
										}

										methodStatus.addExpectedWarningIds(expect.getValueAsListOfStrings());
									}
								}

								runTest(method,
										cl.getBeforeTestMethods(),
										cl.getAfterTestMethods(),
										statusReporter,
										methodStatus,
										scan_only,
										classScope);
							}
							catch (TestExecutionError err)
							{
								methodStatus.setError(err);
							}
							catch (Throwable err)
							{
								log.severe("", err);
								methodStatus.setError(new TestExecutionError(err.toString(),
										TestConstants.ERR_UNCAUGHT_EXCEPTION,
										err));
							}

							// check to see if the any expected failures or errors occurred
							String expectedErrorId = methodStatus.getExpectedErrorId();

							if (expectedErrorId != null)
							{
								if (methodStatus.hasError())
								{
									if (!methodStatus.getError().getErrorId().equals(expectedErrorId))
									{
										methodStatus.addFailure(new TestAssertionFailure("Error '"
												+ expectedErrorId
												+ "' expected but '"
												+ methodStatus.getError().getErrorId()
												+ "' caught", TestConstants.FAIL_EXPECTED_EXCEPTION));
									}
									else
									{
										// this test passes
										methodStatus.setExpectedErrorOverride(true);
									}
								}
								else
								{
									methodStatus.addFailure(new TestAssertionFailure("Error '" + expectedErrorId + "' expected",
											TestConstants.FAIL_EXPECTED_EXCEPTION));
								}
							}

							List failureIds = methodStatus.getExpectedFailureIds();

							if (failureIds.size() != 0)
							{
								List assertionFailureIds = methodStatus.getAssertionFailureIds();
								if (failureIds.containsAll(assertionFailureIds) && assertionFailureIds.containsAll(failureIds))
								{
									// this test passes
									methodStatus.setExpectedFailureOverride(true);
								}
								else
								{
									methodStatus.addFailure(new TestAssertionFailure("Failure(s) "
											+ failureIds
											+ " expected but "
											+ assertionFailureIds
											+ " caught", TestConstants.FAIL_EXPECTED_ASSERTION));
								}
							}

							List warningIds = methodStatus.getExpectedWarningIds();

							if (warningIds.size() != 0)
							{
								List testWarningIds = methodStatus.getTestWarningIds();
								if (warningIds.containsAll(testWarningIds) && testWarningIds.containsAll(warningIds))
								{
									// nothing to do here because warnings can't fail a method except for expected warnings not happening
								}
								else
								{
									methodStatus.addFailure(new TestAssertionFailure("Warning(s) "
											+ warningIds
											+ " expected but "
											+ testWarningIds
											+ " caught", TestConstants.FAIL_EXPECTED_WARNING));
								}
							}

							if (statusReporter != null)
							{
								statusReporter.testCompleted(method, methodStatus);
							}
						}
					}
				}

				try
				{
					// now broadcast all the after class methods.  Any failures here will break
					// the entire chain.
					List afterClassMethods = cl.getAfterClassMethods();
					Iterator acit = afterClassMethods.iterator();

					while (acit.hasNext() && !classStatuses.hasFailures())
					{
						ETLTestMethod bcmethod = acit.next();

						broadcast(bcmethod, classStatuses, scan_only, classScope);
					}
				}
				catch (TestExecutionError err)
				{
					classStatuses.setError(err);
				}

				if (!scan_only)
				{
					try
					{
						listener.end(cl, classScope);
					}
					catch (TestExecutionError err)
					{
						classStatuses.setError(err);
					}
					catch (TestAssertionFailure err)
					{
						classStatuses.addFailure(err);
					}
					catch (TestWarning err)
					{
						classStatuses.addWarning(err);
					}
				}
			}
			else
			{
				log.info("Test class not accepted: " + cl.getQualifiedName());
			}
		}

		director.endBroadcast();

		if (statusReporter != null)
		{
			statusReporter.scanCompleted();
		}

		return testCount;
	}

	private void runTest(ETLTestMethod method, List beforeTestMethods, List afterTestMethods, StatusReporter statusReporter, StatusReporter.CompletionStatus methodStatus, boolean scan_only, VariableContext classScope)
			throws TestExecutionError
	{
		// create a nested scope
		VariableContext methodScope = classScope.createNestedScope();

		if (!scan_only)
		{
			try
			{
				listener.begin(method, methodScope);
			}
			catch (TestExecutionError err)
			{
				methodStatus.setError(err);
			}
			catch (TestAssertionFailure err)
			{
				methodStatus.addFailure(err);
			}
			catch (TestWarning err)
			{
				methodStatus.addWarning(err);
			}
		}

		// hit the before test methods
		Iterator bit = beforeTestMethods.iterator();

		while (bit.hasNext())
		{
			ETLTestMethod bmethod = bit.next();

			broadcast(bmethod, methodStatus, scan_only, methodScope);
		}

		if (!methodStatus.hasFailures())
		{
			broadcast(method, methodStatus, scan_only, methodScope);
		}

		// hit the after test methods
		Iterator ait = afterTestMethods.iterator();

		while (ait.hasNext())
		{
			ETLTestMethod amethod = ait.next();

			broadcast(amethod, methodStatus, scan_only, methodScope);
		}

		if (!scan_only)
		{
			try
			{
				listener.end(method, methodScope);
			}
			catch (TestExecutionError err)
			{
				methodStatus.setError(err);
			}
			catch (TestAssertionFailure err)
			{
				methodStatus.addFailure(err);
			}
			catch (TestWarning err)
			{
				methodStatus.addWarning(err);
			}
		}
	}

	private void broadcast(ETLTestMethod next, StatusReporter.CompletionStatus status, boolean scan_only, final VariableContext methodContext)
			throws TestExecutionError
	{
		List methodDefaults = next.getAnnotations("@OperationDefault");
		List classDefaults = next.getTestClass().getAnnotations("@OperationDefault");

		final Map defaultMap = new HashMap();

		// grab the class defaults first.  Just drop these in
		for (ETLTestAnnotation cdefault : classDefaults)
		{
			Map value = cdefault.getValue().getValueAsMap();

			// get the operation name
			ETLTestValueObject opName = value.get("operation");

			defaultMap.put(opName.getValueAsString(), value.get("defaultValue"));
		}

		// grab the method defaults.  Overlay if a class default is present
		for (ETLTestAnnotation mdefault : methodDefaults)
		{
			Map value = mdefault.getValue().getValueAsMap();

			// get the operation name
			ETLTestValueObject opName = value.get("operation");

			String opNameValueAsString = opName.getValueAsString();

			ETLTestValueObject defaultValue = value.get("defaultValue");

			if (!defaultMap.containsKey(opNameValueAsString))
			{
				defaultMap.put(opNameValueAsString, defaultValue);
			}
			else
			{
				// merge it in
				ETLTestValueObject currentValue = defaultMap.get(opNameValueAsString);

				// use a right-merge so that this new value will override
				defaultMap.put(opNameValueAsString,
						currentValue.merge(defaultValue, ETLTestValueObject.merge_type.right_merge));
			}
		}

		List ops = next.getOperations();

		Iterator it = ops.iterator();

		while (it.hasNext())
		{
			ETLTestOperation op = it.next();

			if (director.accept(op) == ClassResponder.response_code.accept)
			{
				if (!scan_only)
				{
					try
					{
						// TODO - lookup this operation against the map and merge defaults if found
						ETLTestValueObject opDefaults = defaultMap.get(op.getOperationName());

						ETLTestValueObject toperands = op.getOperands();

						if (opDefaults != null)
						{
							if (toperands != null)
							{
								// this is a left merge.  Defaults can't override actual arguments
								toperands = toperands.merge(opDefaults, ETLTestValueObject.merge_type.left_merge);
							}
							else
							{
								toperands = opDefaults;
							}
						}

						ETLTestValueObject operands = toperands;

						final ExecutionContext econtext = new ExecutionContext()
						{
							@Override
							public void process(ETLTestOperation op, VariableContext vcontext)
									throws TestAssertionFailure, TestExecutionError, TestWarning
							{
							// TODO - lookup this operation against the map and merge defaults if found
							ETLTestValueObject opDefaults = defaultMap.get(op.getOperationName());

							ETLTestValueObject toperands = op.getOperands();

							if (opDefaults != null)
							{
								// this is a left merge.  Defaults can't override actual arguments
								toperands = toperands.merge(opDefaults);
							}

							ETLTestValueObject operands = toperands;

							// handled or reject are both acceptable
							if (listener.process(op, operands, methodContext, this) == ClassResponder.action_code.defer)
							{
								throw new TestExecutionError("Listener could not handle operation: "
										+ op.getOperationName()
										+ ": "
										+ (operands != null ? operands.getJsonNode() : "{}"), TestConstants.ERR_INVALID_OPERATION);
							}
							}
						};

						listener.begin(op, operands, methodContext, econtext);

						try
						{
							if (listener.process(op, operands, methodContext, econtext) == ClassResponder.action_code.defer)
							{
								throw new TestExecutionError("Listener could not handle operation: " + op.getOperationName() + ": " + (
										operands != null
												? operands.getJsonNode()
												: "{}"), TestConstants.ERR_INVALID_OPERATION);
							}
						}
						finally
						{
							listener.end(op, operands, methodContext, econtext);
						}
					}
					catch (TestAssertionFailure asrt)
					{
						status.addFailure(asrt);
					}
					catch (TestWarning warn)
					{
						status.addWarning(warn);
					}
					catch (TestExecutionError thr)
					{
						throw thr;
					}
					catch (Throwable thr)
					{
						throw new TestExecutionError("Untrapped error", TestConstants.ERR_UNCAUGHT_EXCEPTION, thr);
					}
				}
			}
			else
			{
				log.info("Test operation not accepted:  " + op.getQualifiedName());
			}
		}
	}

	@Override
	public void reset()
	{
		locator.reset();
	}

	public VariableContext getVariableContext()
	{
		return context;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy