package uk.ac.uea.threadr.internal.testing;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import uk.ac.uea.threadr.ThreadSafety;
import uk.ac.uea.threadr.ThreadTest;
import uk.ac.uea.threadr.Threadr;
import uk.ac.uea.threadr.internal.testing.tests.MemberTest;
import uk.ac.uea.threadr.internal.testing.tests.SerializableTest;
import uk.ac.uea.threadr.internal.testing.tests.StaticTester;
/**
* Tests the thread safety of Java objects using a set of tests described using
* the {@link ThreadTest} interface provided by the Threadr API. The results
* of the testing are returned as a value from the {@link ThreadSafety} enum.
*
* @author Jordan Woerner
* @version 1.2
*/
public class SafetyTester {
private static final Logger logger = Threadr.logger;
/** A List of ThreadTests to use when testing for thread safety. */
private Set<ThreadTest> tests;
/** The safety of the thread tests. */
private ThreadSafety safety;
/**
* Sets up the SafetyTester loading all the tests provided by the
* framework.
*
* @since 1.0
*/
public SafetyTester() {
this.tests = new HashSet<>();
this.safety = ThreadSafety.THREAD;
tests.add(new StaticTester());
tests.add(new MemberTest());
}
/**
* Passes the SafetyTester a list of tests from outside of the framework.
*
* <p>The {@link Set} of tests are required to implement the
* {@link ThreadTest} interface to be accepted.</p>
*
* @param newTests A List of tests derived from the ThreadTest interface.
* @since 1.0
*/
public void provideTests(Set<? extends ThreadTest> newTests) {
for (Object test : newTests) {
this.tests.add((ThreadTest)test);
}
}
/**
* Test the safety of the provided class using the tests know by this
* SafetyTester.
*
* @param obj The {@link Object} to test for thread safeness.
* @return Returns a value from the {@link ThreadSafety} enum. Returns
* {@link ThreadSafety#THREAD} if the class being tested is thread safe,
* {@link ThreadSafety#VM} if the class being tested can be run in a new Java
* Virtual Machine instance, or {@link ThreadSafety#SEQUENTIAL} if the class
* cannot be run in parallel in any manner.
* @since 1.0
*/
public ThreadSafety testSafety(final Object obj) {
this.safety = ThreadSafety.THREAD; // Reset the safety every run.
List<Class<?>> failedTests = new ArrayList<>();
Class<?> clazz = obj.getClass();
logger.fine(String.format("Testing: %s", clazz.getName()));
/* Use every test provided to see if the class is thread safe. */
for (Object t : tests) {
ThreadTest test = (ThreadTest)t;
ThreadSafety result = test.test(clazz);
if (result != ThreadSafety.THREAD) {
/* Any test that is not thread safe is failed. */
failedTests.add(test.getClass());
}
/* If a result was returned, and it's worse than the stored one. */
if (result != null
&& (result.val() < safety.val())) {
safety = result; // Set the new safety result.
}
}
/* Check the task can be serialised if it's VM safe. */
if (safety == ThreadSafety.VM
&& !isSerialisabe(obj)) {
/* If a task cannot be serialised, it's unsafe. */
safety = ThreadSafety.SEQUENTIAL;
}
/* Print out the thread safeness of the class. */
switch (safety) {
case SEQUENTIAL:
logger.info(String.format("Class %s - Sequential safe",
clazz.getName()));
break;
case THREAD:
logger.info(String.format("Class %s - Thread safe",
clazz.getName()));
break;
case VM:
logger.info(String.format("Class %s - Virtual Machine safe",
clazz.getName()));
break;
default:
logger.info(String.format("Unknown test result for class %s",
clazz.getName()));
}
/* Print any failed tests to the loggers fine output. */
if (failedTests.size() > 0) {
StringBuilder errors = new StringBuilder("Failed tests:\n");
for (Class<?> test : failedTests) {
errors.append('\t').append(test.getName()).append('\n');
}
logger.fine(errors.toString());
}
return safety; // Return the thread safeness of the class.
}
/**
* Tests a provided object to see if it can be serialised and
* de-serialised safely.
*
* @param object The Object to test.
* @return Returns true if the Object serialised fully and safely, false
* otherwise.
* @since 1.2
*/
public boolean isSerialisabe(Object object) {
return new SerializableTest().test(object);
}
}