/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ensembl.healthcheck.eg_gui;
import java.awt.BorderLayout;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Arrays;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import org.ensembl.healthcheck.DatabaseRegistry;
import org.ensembl.healthcheck.DatabaseRegistryEntry;
import org.ensembl.healthcheck.ReportLine;
import org.ensembl.healthcheck.ReportManager;
import org.ensembl.healthcheck.eg_gui.GuiTestResultWindowTab;
import org.ensembl.healthcheck.eg_gui.TestProgressDialog;
import org.ensembl.healthcheck.testcase.AbstractPerlBasedTestCase;
import org.ensembl.healthcheck.testcase.EnsTestCase;
import org.ensembl.healthcheck.testcase.MultiDatabaseTestCase;
import org.ensembl.healthcheck.testcase.OrderedDatabaseTestCase;
import org.ensembl.healthcheck.testcase.PerlScriptConfig;
import org.ensembl.healthcheck.testcase.SingleDatabaseTestCase;
import org.ensembl.healthcheck.util.ConnectionPool;
import java.sql.Connection;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GuiTestRunner {
/**
* <p>
* Creates a logger that will forward any logged messages to the Report
* Manager.
* </p>
*
* @param h
* @return Logger
*
*/
protected static Logger createGuiLogger(Handler h) {
Logger logger = Logger.getAnonymousLogger();
for (Handler currentHandler : logger.getHandlers()) {
logger.removeHandler(currentHandler);
}
// Otherwise messages will be sent to the screen.
//
logger.setUseParentHandlers(false);
logger.addHandler(h);
if (logger.getLevel()==null) {
logger.setLevel(Constants.defaultLogLevel);
}
return logger;
}
/**
* <p>
* Run all the tests in a list.
* </p>
*
* @param tests
* @param databases
* @param testProgressDialog
* @param PERL5LIB
* @param psc
* @param guiLogHandler
* @return Thread running the tests
*/
public static Thread runAllTests(
final List<Class<? extends EnsTestCase>> tests,
final DatabaseRegistryEntry[] databases,
final TestProgressDialog testProgressDialog,
//final JComponent resultDisplayComponent,
final String PERL5LIB,
final PerlScriptConfig psc,
final GuiLogHandler guiLogHandler
) {
// Tests are run in a separate thread
//
Thread t = new Thread() {
public void run() {
PrintStream stderrSaved = System.err;
testProgressDialog.reset();
testProgressDialog.setVisible(true);
int totalTestsToRun = tests.size() * databases.length;
testProgressDialog.setMaximum(totalTestsToRun);
int testsRun = 0;
// for each test, if it's a single database test we run it against each
// selected database in turn
// for multi-database tests, we create a new DatabaseRegistry containing
// the selected tests and use that
//
for (Class<? extends EnsTestCase> currentTest : tests) {
// If there was an interrupt request for this thread, no
// more tests are executed.
//
if (isInterrupted()) {
break;
}
// Create a logger with this handler
Logger guiLogger = createGuiLogger(guiLogHandler);
// Inject into the current testcase. The logger property
// is static. It should be set before instantiation in
// case something is done with the logger in the
// constructor. (As in AbstractPerlModuleBasedTestCase)
//
EnsTestCase.setLogger(guiLogger);
EnsTestCase testCase = null;
try {
testCase = currentTest.newInstance();
}
catch (InstantiationException e) { throw new RuntimeException(e); }
catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// Inject a logger that will forward all logging messages
// to the gui.
//
Logger savedLogger = testCase.getLogger();
// Tell the guiloghandler to associate all log messages
// with the current testcase.
//
guiLogHandler.setEnsTestCase(testCase);
// System properties are set by the GUI. This prevents
// EnsTestCase.importSchema from overwriting settings
// by the user with the defaults from the configuration
// file.
//
testCase.setSetSystemProperties(false);
// Stack traces are written to stderr by tests. Stderr is
// redirected to the logger.
//
System.setErr(new ReporterPrintStream(guiLogger, Level.SEVERE, testCase));
// If PERL5LIB parameter has been set and this is a perl
// based test case, then set the PERL5LIB attribute.
//
if (testCase instanceof AbstractPerlBasedTestCase) {
AbstractPerlBasedTestCase at = (AbstractPerlBasedTestCase) testCase;
if (PERL5LIB != null) {
at.setPERL5LIB(PERL5LIB);
}
if (psc != null) {
at.setConfig(psc);
}
}
boolean passed = false;
if (testCase instanceof SingleDatabaseTestCase) {
for (DatabaseRegistryEntry currentDbre : databases) {
String message = testCase.getShortTestName() + ": " + currentDbre.getName();
ReportManager.startTestCase(testCase, currentDbre);
testProgressDialog.setNote(message);
testCase.types();
try {
passed = ((SingleDatabaseTestCase) testCase).run(currentDbre);
}
catch (Exception e) {
ReportManager.report(
testCase,
currentDbre.getConnection(),
ReportLine.PROBLEM,
testCase.getShortTestName() + " threw an exception:"
+ e.getClass().getCanonicalName() + "\n\n"
+ stackTraceToString(e.getStackTrace()) + "\n\n"
+ e.getMessage()
);
}
catch (java.lang.Error e) {
String errorMsg = testCase.getShortTestName() + " threw a java error:"
+ e.getClass().getCanonicalName() + "\n\n"
+ stackTraceToString(e.getStackTrace()) + "\n\n"
+ e.getMessage();
System.err.println(errorMsg);
System.out.println(errorMsg);
stderrSaved.println(errorMsg);
ReportManager.report(
testCase,
currentDbre.getConnection(),
ReportLine.PROBLEM,
errorMsg
);
}
// If a test has not reported anything to the
// report manager, there will not be any report.
// The user may think that the test was not run.
// So in this case a standard line is generated.
//
boolean testHasReportedSomething = ReportManager.getAllReportsByTestCase().containsKey(testCase.getTestName());
if (!testHasReportedSomething) {
if (passed) {
ReportManager.report(
testCase,
currentDbre.getConnection(),
ReportLine.INFO,
testCase.getShortTestName() + " did not produce any output, but reported that the database has passed."
);
} else {
ReportManager.report(
testCase,
currentDbre.getConnection(),
ReportLine.PROBLEM,
testCase.getShortTestName() + " did not produce any output, but reported that the database has failed."
);
}
}
ReportManager.finishTestCase(testCase, passed, currentDbre);
testsRun += 1;
testProgressDialog.setProgress(testsRun);
testProgressDialog.repaint();
}
} else if (testCase instanceof MultiDatabaseTestCase) {
DatabaseRegistry dbr = new DatabaseRegistry(databases);
ReportManager.startTestCase(testCase, null);
String message = testCase.getShortTestName() + " ( " + dbr.getEntryCount() + " databases)";
testProgressDialog.setNote(message);
testCase.types();
try {
passed = ((MultiDatabaseTestCase) testCase).run(dbr);
}
catch (Exception e) {
ReportManager.report(
testCase,
(Connection) null,
ReportLine.PROBLEM,
testCase.getShortTestName() + " threw an exception:"
+ e.getClass().getCanonicalName() + "\n\n"
+ stackTraceToString(e.getStackTrace()) + "\n\n"
+ e.getMessage()
);
}
ReportManager.finishTestCase(testCase, passed, null);
// If a test has not reported anything to the
// report manager, there will not be any report.
// The user may think that the test was not run.
// So in this case a standard line is generated.
//
boolean testHasReportedSomething = ReportManager.getAllReportsByTestCase().containsKey(testCase.getTestName());
if (passed && !testHasReportedSomething) {
ReportManager.report(
testCase,
dbr.getAll()[0].getConnection(),
ReportLine.INFO,
testCase.getShortTestName() + " did not produce any output, but reported that the database has passed."
);
}
testsRun += dbr.getEntryCount();
testProgressDialog.setProgress(testsRun);
testProgressDialog.repaint();
} else if (testCase instanceof OrderedDatabaseTestCase) {
JOptionPane.showMessageDialog(
testProgressDialog,
"Functionality for running OrderedDatabaseTestCases has not been implemented!",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
// Retore the original logger. Actually unnecessary,
// because the testcase will not be used anymore.
//
testCase.setLogger(savedLogger);
// Restore stderr
//
System.setErr(stderrSaved);
ReportManager.finishTestCase(
testCase,
passed,
null
);
}
testProgressDialog.setVisible(false);
ConnectionPool.closeAll();
// Open in the legacy result window, because it is really
// nice.
//
//resultDisplayComponent.removeAll();
//resultDisplayComponent.add(
//new GuiTestResultWindowTab("All", ReportLine.ALL),
//BorderLayout.CENTER
//);
// The above will have no visible effect. In order for this
// to work, revalidate must be called.
//
// See: http://www.iam.ubc.ca/guides/javatut99/uiswing/overview/threads.html
//
//resultDisplayComponent.revalidate();
}
};
testProgressDialog.setRunner(t);
t.setName("GuiTestRunner");
UncaughtExceptionHandler eh =
new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
String errorMsg = t.getName() + " threw a java error:"
+ e.getClass().getCanonicalName() + "\n\n"
+ stackTraceToString(e.getStackTrace()) + "\n\n"
+ e.getMessage();
System.err.println(errorMsg);
System.out.println(errorMsg);
}
};
t.setUncaughtExceptionHandler(eh);
Thread.setDefaultUncaughtExceptionHandler(eh);
t.start();
return t;
}
protected static String stackTraceToString(StackTraceElement[] stackTraceElement) {
StringBuffer stacktrace = new StringBuffer();
for (StackTraceElement ste : stackTraceElement) {
stacktrace.append(ste.toString());
stacktrace.append("\n");
}
return stacktrace.toString();
}
}
/**
*
* <p>
* A PrintStream that forwards print statements to the ReportManager as
* problems which will make the current test fail.
* </p>
*
* <p>
* Used to capture printStackTraceEvents that happen during testruns and
* would be ignored otherwise.
* </p>
*
* @author michael
*
*/
class ReporterPrintStream extends PrintStream {
protected EnsTestCase e;
protected Logger logger;
protected Level logLevel;
public ReporterPrintStream(Logger logger, Level logLevel, EnsTestCase e) {
super(System.out);
this.e = e;
this.logger = logger;
this.logLevel = logLevel;
}
public void print(String s) {
// The ReportManager can't be used like this here:
//
// ReportManager.problem(e, (Connection) null, s);
//
// because in the event that more than ReportManager.MAX_BUFFER_SIZE
// lines are reported, the ReportManager will write to System.err
// which will make these two methods call each other recursively and
// lead to a java.lang.StackOverflowError.
//
// Instead, error messages are forwarded to the logger.
logger.log(logLevel, s);
}
public void println(String s) {
// No newline added, the logger adds a newline already.
// a newline.
//
this.print(s);
}
public void println() {
// Explicit newlines will be added.
//
this.print("\n");
}
}