package org.mitre.test;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* Abstract Base Test implementation of TestUnit.
*
* @author Jason Mathews, MITRE Corp.
* Date: 2/20/12 11:56 AM
*/
public abstract class BaseTest implements TestUnit {
protected final Logger log;
private StatusEnumType status;
private String description;
private final Set<String> warnings = new LinkedHashSet<String>();
private HttpResponse response;
private boolean keepResponse;
private final Set<TestUnit> depends = new TreeSet<TestUnit>();
/**
* keepResponse property if true flags the test component to keep the
* HTTP response object so dependent tests can fetch it.
*/
public static final String PROP_KEEP_RESPONSE_BOOL = "keepResponse";
private List<Tuple> properties;
public BaseTest() {
log = LoggerFactory.getLogger(getClass());
}
/**
* Associate a prerequisite that this test is dependent upon such that if any of its
* prerequisites fail then this test is not executed and assumed to fail. The classes
* associated with the prerequisites must be contained in those returned by calling
* {@link #getDependencyClasses}.
*
* @param aTest a required prerequisite test that this test is dependent upon
* @throws IllegalArgumentException if attempt to add dependency on itself
*/
public void addDependency(TestUnit aTest) {
if (aTest == this) throw new IllegalArgumentException("Test cannot depend on itself");
depends.add(aTest);
// assume that any TestUnit object added here has its class contained in the list returned by getDependencyClasses()
// since that is the source for invoking this method.
}
/**
* Get list of prerequisite or dependent tests associated with this test
* @return non-null list, empty if no prerequisites are applicable
*/
@NonNull
public Set<? extends TestUnit> getDependencies() {
return depends;
}
@CheckForNull
public TestUnit getDependency(Class<? extends TestUnit> testClass) {
for (TestUnit test : depends) {
if (testClass == test.getClass()) return test;
}
// if returns null then error in test
return null;
}
public boolean equals(Object other) {
return other instanceof TestUnit && this.equals((TestUnit) other);
}
public boolean equals(TestUnit other) {
return other != null && this.getId().equals(other.getId());
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
* @param other the <code>TestUnit</code> to be compared.
* @return the value <code>0</code> if the argument other is equal to
* this TestUnit by its id; a value less than <code>0</code> if the id
* for this Test is lexicographically less than the other argument; and a
* value greater than <code>0</code> if id for this test is
* lexicographically greater than the other's id.
* @exception NullPointerException if other is null
*/
public int compareTo(TestUnit other) {
// if (other == null) return 1;
return getId().compareTo(other.getId());
}
/**
* Returns a hash code for this TestUnit based on its unique identifier.
*
* @return a hash code value for this object.
*/
public int hashCode() {
return getId().hashCode();
}
/**
* Add warning message to the test results
*
* @param msg Warning message, ignored if null
* @return <tt>true</tt> if the warning set did not already contain the specified
* message (i.e. the warning has not been seen for this test)
*/
public boolean addWarning(String msg) {
return msg != null && getWarnings().add(msg);
}
/**
* Add warning message to the test results and log
* if message has not already been logged in this test.
* @param msg Warning message, ignored if null
* @return <tt>true</tt> if this warning set did not already contain the specified
* message (i.e. the warning has not been seen for this test)
*/
public boolean addLogWarning(String msg) {
boolean ret = addWarning(msg);
if (ret) log.warn(msg);
return ret;
}
/**
* Get list of warnings, empty if no warnings were generated
*
* @return ist, never null
*/
@NonNull
public Set<String> getWarnings() {
return warnings;
}
/**
* Set status on the test
*
* @param status Status code, preferably non-null
*/
public void setStatus(StatusEnumType status) {
this.status = status;
this.description = null;
}
/**
* Set status on the test with an optional description (or reason).
*
* @param status Status code, preferably non-null
* @param description Description for why status is what it is or <tt>null</tt> otherwise
*/
public void setStatus(StatusEnumType status, String description) {
this.status = status;
this.description = description;
}
/**
* Get final execution status of the test. After execution this should be non-null
* even if an exception is thrown in which it would have a <em>FAILED</em> status.
* @return status
*/
public StatusEnumType getStatus() {
return status;
}
/**
* Get status description associated with the final disposition of
* executing or not executing this test.
* @return description, null if not defined
*/
@Nullable
public String getStatusDescription() {
return description;
}
/**
* Get name (or short description) of the test
* @return name
*/
@NonNull
public String getName() {
return getId();
}
protected boolean isKeepResponse() {
return keepResponse;
}
@Nullable
public HttpResponse getResponse() {
return response;
}
public void setResponse(HttpResponse response) {
this.response = response;
}
protected void dumpResponse(HttpRequestBase req, HttpResponse response) {
ClientHelper.dumpResponse(req, response, false);
}
protected void dumpResponse(HttpRequestBase req, HttpResponse response, boolean dumpEntity) {
ClientHelper.dumpResponse(req, response, dumpEntity);
}
/**
* Set deferred property on this class that will be set on the target <em>testClass</em>
* instance after the instance is created and added to the execution plan list as
* well as any other dependencies for this test are also loaded. It is required
* for the <tt>testClass</tt> argument called here to be contained in the list
* returned by calling the TestUnit's {@link #getDependencyClasses} method
* otherwise test will not be executed.
*
* @param testClass, never null
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
*/
protected void setProperty(Class<? extends TestUnit> testClass, String key, Object value) {
/*
// checking properties on only declared dependent classes is enforced in ExecutionPlan.add()
if (!getDependencyClasses().contains(testClass)) {
throw new IllegalStateException("Test add dependency to class=" + testClass.getName());
}
*/
if (properties == null) {
properties = new ArrayList<Tuple>();
}
properties.add(new Tuple(testClass, key, value));
}
/**
* Set property on this test.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @throws IllegalArgumentException if key not handled by class
* @exception ClassCastException if target type does not match expected type typically indicated
* as ending of the property name constant (e.g. PROP_KEEP_RESPONSE_BOOL)
*/
public void setProperty(String key, Object value) {
// System.out.printf("XXX: setProperty %s: %s%n", key, value);
if (PROP_KEEP_RESPONSE_BOOL.equals(key)) {
// setKeepContent((Boolean)value);
this.keepResponse = (Boolean)value;
} else {
// must be implemented by sub-classes if properties apply otherwise IllegalArgumentException
throw new IllegalArgumentException("setProperty " + key + " + not implemented");
}
}
/**
* Get list of deferred properties for this test that will be set on the
* named classes only if all prerequisite tests for this test are also loaded.
* @return list of properties, empty if not set
*/
@NonNull
public List<Tuple> getProperties() {
return properties == null ? Collections.<Tuple>emptyList() : properties;
}
/**
* Cleanup after test is executed. Some tests may need to keep its state
* (e.g., HTTP response or created DOM instance) for other tests that are
* dependent on its results.
*/
public void cleanup() {
if (!keepResponse || status != StatusEnumType.SUCCESS) {
response = null; // clear response - no longer needed
} // else System.out.println("XXX: keep HTTP results"); // debug
}
// start of "junit"-like methods
protected void assertEquals(String expected, String actual) throws TestException {
if (expected == null) {
if (actual == null) return;
fail("Expected null but was: <" + actual + ">");
} else if (!expected.equals(actual)) {
fail("Expected <" + expected + "> but was: <" + actual + ">");
}
}
protected void assertEquals(int expected, int actual) throws TestException {
if (expected != actual) {
fail("Expected <" + expected + "> but was: <" + actual + ">");
}
}
protected void assertTrue(boolean cond, String msg) throws TestException {
if (!cond) fail(msg);
}
protected void assertFalse(boolean cond, String msg) throws TestException {
if (cond) fail(msg);
}
protected void fail(String s) throws TestException {
throw new TestException(s);
}
// end of "junit"-like methods
}