org.etlunit.ClassBroadcasterImpl Maven / Gradle / Ivy
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