/**
* <copyright>
*
* This program and the accompanying materials are made available under the
* terms of the MIT license (X11 license) which accompanies this distribution.
*
* </copyright>
*/
package rtt.core.manager;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import rtt.core.archive.Archive;
import rtt.core.archive.configuration.Classpath;
import rtt.core.archive.configuration.Configuration;
import rtt.core.archive.logging.Detail;
import rtt.core.archive.logging.EntryType;
import rtt.core.archive.testsuite.Testcase;
import rtt.core.archive.testsuite.Testsuite;
import rtt.core.archive.testsuite.VersionData;
import rtt.core.exceptions.RTTException;
import rtt.core.exceptions.RTTException.Type;
import rtt.core.loader.ArchiveLoader;
import rtt.core.manager.data.ConfigurationManager.ConfigStatus;
import rtt.core.manager.data.LogManager;
import rtt.core.manager.data.TestsuiteManager;
import rtt.core.manager.data.TestsuiteManager.TestcaseStatus;
import rtt.core.manager.data.history.OutputDataManager;
import rtt.core.manager.data.history.OutputDataManager.OutputDataType;
import rtt.core.testing.Tester;
import rtt.core.testing.compare.results.ITestFailure;
import rtt.core.testing.compare.results.TestResult;
import rtt.core.testing.compare.results.TestResult.ResultType;
import rtt.core.testing.generation.DataGenerator;
import rtt.core.testing.generation.Executor;
import rtt.core.utils.GenerationInformation;
import rtt.core.utils.GenerationInformation.GenerationResult;
import rtt.core.utils.GenerationInformation.GenerationType;
import rtt.core.utils.RTTLogging;
/**
*
* @author Peter Mucha
*
*/
public class Manager {
/**
* This modes specify, which action should take for adding test cases.
* @author Christian Oelsner <C.Oelsner@gmail.com>
* @see #SKIP
* @see #IGNORE
* @see #RENAME
* @see #OVERWRITE
*/
public enum TestCaseMode {
/**
* throw exception, if file already exists
*/
SKIP,
/**
* ignore double files
*/
IGNORE,
/**
* rename new files
*/
RENAME,
/**
* overwrite old file
*/
OVERWRITE
}
File archivePath;
Archive currentArchive;
LogManager currentLog;
private String baseDir;
private ArchiveLoader loader;
public static boolean verbose = true;
public Manager(File archivePath, boolean verbose) throws RTTException {
this.archivePath = archivePath;
this.loader = ArchiveLoader.create(archivePath);
// because sometimes, it is not the same
Thread.currentThread().setContextClassLoader(
this.getClass().getClassLoader());
Manager.verbose = verbose;
}
public Manager(File archive, boolean verbose, ClassLoader classLoader) throws RTTException {
this(archive, verbose);
Thread.currentThread().setContextClassLoader(classLoader);
}
private void initArchive(File archive) {
loader.setBasePath(archive);
baseDir = loader.getBasePath();
currentArchive = new Archive(loader);
currentLog = currentArchive.getLogManager();
}
public void loadArchive(File archive) throws RTTException {
if (!archive.exists()) {
throw new RTTException(Type.NO_ARCHIVE, archive + " not found.");
}
initArchive(archive);
try {
currentArchive.load();
} catch (Exception e) {
throw new RTTException(Type.NO_ARCHIVE, "Could not load archive.", e);
}
}
public void loadArchive(File archive, String config) throws RTTException {
loadArchive(archive);
boolean hasChanged = currentArchive.setActiveConfiguration(config);
if (hasChanged) {
RTTLogging.info("Active configuration: " + config);
}
}
public void createArchive(File archive) throws Exception {
File parentFolder = archive.getParentFile();
if (!parentFolder.exists()) {
parentFolder.mkdirs();
}
initArchive(archive);
currentLog.addEntry(EntryType.ARCHIVE, "Archive created.", "");
currentArchive.save();
}
private boolean isInitialized() {
return (currentArchive != null);
}
private void checkInitialize() throws RTTException {
if (!isInitialized()) {
throw new RTTException(Type.NO_ARCHIVE, "No archive loaded.");
}
}
public Archive getArchive() {
return currentArchive;
}
public void exportLog(File location) throws Exception {
checkInitialize();
currentLog.export(location);
}
public void printArchiveInformations() throws Exception {
checkInitialize();
RTTLogging.info("Informations of archive:");
currentArchive.print();
}
public void saveArchive(File archivePath) throws RTTException {
checkInitialize();
try {
currentArchive.save();
} catch (Exception e) {
throw new RTTException(Type.NO_ARCHIVE, "Could not save archive.", e);
}
}
/**
* This function sets a {@link Configuration} in this {@link Archive}. If the configuration does not exist,
* it will be created. If a configuration with the given name exists, it will be updated unless the overwrite flag is set.
* <p>
* A {@link ConfigStatus} object will be returned to indicate which actions are taken by this function. See {@link ConfigStatus#ADDED}, {@link ConfigStatus#UPDATED} and {@link ConfigStatus#SKIPPED}.
*
* @param configName the name of the configuration
* @param initialNode the name of initial node
* @param cpEntries a list of entries, which should be added to the class path.
* @param defaultConfig indicates, if new configuration should be the default configuration of this archive
* @param overwrite indicates, if an existing configuration should be overwritten
* @return a {@link ConfigStatus} object, indicating the taken actions
* @see ConfigStatus#ADDED
* @see ConfigStatus#UPDATED
* @see ConfigStatus#SKIPPED
*/
public ConfigStatus setConfiguration(String configName, String initialNode,
List<String> cpEntries, boolean defaultConfig, boolean overwrite) {
List<Detail> infos = new LinkedList<Detail>();
Configuration config = new Configuration();
config.setName(configName);
Classpath cPath = new Classpath();
cPath.getPath().addAll(cpEntries);
config.setClasspath(cPath);
config.setInitialNode(initialNode);
ConfigStatus state = currentArchive.setConfiguration(config, overwrite);
String message = null;
switch (state) {
case ADDED:
message = "Configuration added: ";
break;
case UPDATED:
message = "Configuration updated: ";
break;
case SKIPPED:
message = "Configuration not changed: ";
break;
}
if (state.initialNodeSet) {
Detail detail = new Detail();
detail.setMsg("Initial Node:");
detail.setSuffix(initialNode);
detail.setPriority(0);
infos.add(detail);
}
for (String entry : state.newEntries) {
Detail pathInfo = new Detail();
pathInfo.setPriority(1);
pathInfo.setMsg("Added classpath entry: ");
pathInfo.setSuffix(entry);
infos.add(pathInfo);
}
for (String entry : state.deletedEntries) {
Detail pathInfo = new Detail();
pathInfo.setPriority(1);
pathInfo.setMsg("Removed classpath entry: ");
pathInfo.setSuffix(entry);
infos.add(pathInfo);
}
currentLog.addEntry(EntryType.ARCHIVE, message, configName, infos);
currentArchive.setActiveConfiguration(configName);
if (defaultConfig) {
setDefaultConfiguration(configName);
}
return state;
}
public void setDefaultConfiguration(String configName) {
if (currentArchive.setDefaultConfiguration(configName) && verbose) {
currentLog.addEntry(EntryType.INFO,
"Default Configuration set: ", configName);
}
}
public boolean createTestSuite(String suiteName) {
boolean result = currentArchive.addTestsuite(suiteName);
if (result) {
currentLog.addEntry(EntryType.ARCHIVE, "Test suite created: ",
suiteName);
}
return result;
}
public boolean removeTestsuite(String suiteName) throws RTTException {
checkInitialize();
if (!currentArchive.hasTestsuite(suiteName)) {
throw new RTTException(Type.DATA_NOT_FOUND, "Cannot find test suite: "
+ suiteName);
}
List<Testcase> caseList = currentArchive.getTestcases(suiteName);
List<Detail> detailList = new ArrayList<Detail>();
// first, remove all test cases
for (Testcase testcase : caseList) {
Detail detail = new Detail();
detail.setSuffix(testcase.getName());
detail.setPriority(0);
if (currentArchive.removeTestcase(suiteName, testcase.getName())) {
detail.setMsg("Test case removed: ");
} else {
detail.setMsg("Test case could not be removed: ");
}
detailList.add(detail);
}
boolean suiteRemoved = currentArchive.removeTestsuite(suiteName);
String message = "Test suite could not be removed: ";
if (suiteRemoved) {
message = "Test suite removed: ";
}
currentLog.addEntry(EntryType.ARCHIVE, message, suiteName, detailList);
return suiteRemoved;
}
/**
* Adds a set of files to the given test suite
*
* @param files
* @param testSuite
* @param mode
* @throws Exception
*/
public List<RTTException> addAllFiles(List<File> files, String testSuite,
TestCaseMode mode) {
List<Detail> detailList = new LinkedList<Detail>();
List<RTTException> exceptions = new ArrayList<RTTException>();
for (File f : files) {
try {
detailList.add(addFile(f, testSuite, mode));
} catch (RTTException e) {
exceptions.add(e);
}
}
currentLog.addEntry(EntryType.ARCHIVE,
"Test suite modified: ", testSuite, detailList);
return exceptions;
}
/**
*
* @param file
* @param suiteName
* @param mode
* @return testinformation
* @throws Exception
*/
protected Detail addFile(File file, String suiteName, TestCaseMode mode)
throws RTTException {
checkInitialize();
Testsuite t = currentArchive.getTestsuite(suiteName, false);
if (t == null) {
if (createTestSuite(suiteName) == false) {
throw new RTTException(Type.OPERATION_FAILED,
"Could not create test suite.");
}
t = currentArchive.getTestsuite(suiteName, false);
}
String caseName = TestsuiteManager.getCaseName(file);
TestcaseStatus status = currentArchive.addTestcase(suiteName, file,
mode);
Detail detail = new Detail();
detail.setSuffix(file.getAbsolutePath());
detail.setPriority(0);
String message = "Test case '" + caseName + "' ";
switch (status) {
case NEW:
message += "added: ";
break;
case UPDATE:
message += "updated: ";
detail.setPriority(1);
break;
case ERROR:
message += "had an error: ";
detail.setPriority(1);
break;
case NONE:
message += "not changed: ";
break;
default:
message = "Unknown operation on " + message;
break;
}
detail.setMsg(message);
RTTLogging.info(detail.getMsg() + detail.getSuffix());
return detail;
}
public void removeTest(String suiteName, String caseName)
throws RTTException {
checkInitialize();
if (!currentArchive.hasTestcase(suiteName, caseName)) {
throw new RTTException(Type.DATA_NOT_FOUND, "Cannot find test case: "
+ suiteName + "/" + caseName);
}
currentArchive.removeTestcase(suiteName, caseName);
currentLog.addEntry(EntryType.ARCHIVE, "Test case removed: ",
suiteName + "/" + caseName);
}
public void setParametersToTest(String suiteName, String caseName, List<String> parameters) throws RTTException {
checkInitialize();
Testcase testcase = currentArchive.getTestcase(suiteName, caseName);
if (testcase != null) {
testcase.getParameter().clear();
testcase.getParameter().addAll(parameters);
currentLog.addEntry(EntryType.ARCHIVE, "Parameters set for test case: ", suiteName + "/" + caseName);
}
}
public GenerationInformation generateTests(String suiteName)
throws Exception {
GenerationInformation results = new GenerationInformation(GenerationType.REFERENCE_DATA);
if (suiteName == null) {
for (Testsuite suite : currentArchive.getTestsuites(false)) {
results.concat(generateTests(suite.getName()));
}
} else {
results.concat(generateTests(suiteName,
currentArchive.getActiveConfiguration()));
}
return results;
}
public GenerationInformation generateTests(String suiteName,
Configuration config) throws Exception {
checkInitialize();
if (!currentArchive.hasTestsuite(suiteName)) {
throw new RTTException(Type.DATA_NOT_FOUND, "Test suite '" + suiteName
+ "' does not exist.");
}
GenerationInformation genInfos = new GenerationInformation(GenerationType.REFERENCE_DATA);
RTTLogging.info("Test suite: " + suiteName + " - Configuration: " + config.getName());
Executor executor = DataGenerator.locateInitialNode(config, baseDir);
RTTLogging.info("**** Generate reference data ****");
for (Testcase tcase : currentArchive.getTestcases(suiteName)) {
// load reference data for the test case
OutputDataManager refManager = new OutputDataManager(
currentArchive.getLoader(), suiteName, tcase.getName(),
config, OutputDataType.REFERENCE);
// create new reference data
GenerationResult result = refManager.createData(executor,
tcase.getInputID(), tcase.getParameter());
StringBuilder infoMessage = new StringBuilder();
infoMessage.append("[" + suiteName + "/" + tcase.getName() + "]");
if (result.noError) {
// No error during the generation of the reference data
refManager.save();
infoMessage.append(" has been generated");
// if reference data has replaced, update version data
if (result.hasReplaced) {
String configName = config.getName();
VersionData versionData = currentArchive.getVersionData(tcase, configName, true);
versionData.setReferenceID(versionData.getReferenceID() + 1);
infoMessage.append(" and replaced");
}
} else {
infoMessage.append(" has NOT been generated");
StringBuilder errorMessage = new StringBuilder();
errorMessage.append("[Exception] in Test case [");
errorMessage.append(suiteName + "/" + tcase.getName());
errorMessage.append("]: ");
RTTLogging.error(errorMessage.toString(), result.exception);
}
genInfos.addResult(result);
RTTLogging.info(infoMessage.toString());
}
List<Detail> details = genInfos.makeDetails(true);
String message = "Reference data for test suite [" + suiteName + "]";
if (!details.isEmpty()) {
message += " generated with configuration: ";
} else {
message += " was empty. Configuration used: ";
}
currentLog.addEntry(EntryType.GENERATION, message, config.getName(), details);
return genInfos;
}
public GenerationInformation runTests(String suiteName, boolean matching)
throws Exception {
GenerationInformation results = new GenerationInformation(GenerationType.TEST_DATA);
if (suiteName == null) {
List<Testsuite> suites = currentArchive.getTestsuites(false);
for (Testsuite suite : suites) {
results.concat(runTestsInternal(suite.getName(), matching));
}
} else {
results.concat(runTestsInternal(suiteName, matching));
}
return results;
}
private GenerationInformation runTestsInternal(String suiteName, boolean matching)
throws Exception {
checkInitialize();
if (currentArchive.hasTestsuite(suiteName) == false) {
throw new RTTException(Type.DATA_NOT_FOUND, "Test suite '"
+ suiteName + "' does not exists or has been removed from archive.");
}
GenerationInformation genInfos = new GenerationInformation(GenerationType.TEST_DATA);
Configuration config = currentArchive.getActiveConfiguration();
RTTLogging.info("Test suite: " + suiteName + " - Configuration: " + config.getName());
Executor executor = DataGenerator.locateInitialNode(config, baseDir);
Tester tester = new Tester(currentArchive.getLoader(), matching);
List<TestResult> testResults = new ArrayList<TestResult>();
RTTLogging.info("**** Running tests ****");
for (Testcase tcase : currentArchive.getTestcases(suiteName)) {
// Create new test data manager
OutputDataManager testManager = new OutputDataManager(
currentArchive.getLoader(), suiteName,
tcase.getName(), config, OutputDataType.TEST);
// Create new test data ...
GenerationResult genResult = testManager.createData(executor, tcase.getInputID(), tcase.getParameter());
genInfos.addResult(genResult);
StringBuilder infoMessage = new StringBuilder();
infoMessage.append("[" + suiteName + "/" + tcase.getName() + "]");
infoMessage.append(" has been tested");
// if test data could not have been generated, show skip
TestResult caseResults = new TestResult(ResultType.SKIPPED, suiteName, tcase.getName());
if (genResult.noError) {
testManager.save();
if (genResult.hasReplaced) {
VersionData versionData = currentArchive.getVersionData(tcase, config.getName(), true);
versionData.setTestID(versionData.getTestID() + 1);
}
// do testing
caseResults = tester.test(suiteName, tcase, config);
if (caseResults != null) {
// results for test case are present -> no exceptions occurred
if (caseResults.getType() == ResultType.FAILURE) {
List<ITestFailure> failures = caseResults.getFailures();
StringBuilder warnMessage = new StringBuilder();
for (ITestFailure failure : failures) {
warnMessage.append("[Failure] in Test case [");
warnMessage.append(suiteName + "/" + tcase.getName());
warnMessage.append("]: ");
warnMessage.append(failure.getMessage());
}
RTTLogging.warn(warnMessage.toString());
infoMessage.append(" with failures");
} else {
infoMessage.append(" with no errors");
}
testResults.add(caseResults);
}
} else {
// no results for test case -> exception occurred
StringBuilder errorMessage = new StringBuilder();
errorMessage.append("[Exception] in Test case [");
errorMessage.append(suiteName + "/" + tcase.getName());
errorMessage.append("]: ");
RTTLogging.error(errorMessage.toString(), genResult.exception);
infoMessage.append(" with exception(s)");
}
RTTLogging.info(infoMessage.toString());
}
List<Detail> details = genInfos.makeDetails(false);
if (genInfos.hasErrors() && !details.isEmpty()) {
String message = "Test data for test suite [" + suiteName + "] generated errors with configuration: ";
currentLog.addEntry(EntryType.INFO, message, config.getName(), details);
}
currentLog.addTestrunResult(testResults, config.getName(), suiteName);
return genInfos;
}
public void close() {
archivePath = null;
currentLog = null;
currentArchive.close();
currentArchive = null;
}
}