klayout/src/ut/utMain.cc

1197 lines
30 KiB
C++

/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "utHead.h"
#include "rba.h"
#include "pya.h"
#include "tlStaticObjects.h"
#include "tlTimer.h"
#include "tlFileUtils.h"
#include "layApplication.h"
#include "gsiExpression.h"
#include "gsiExternalMain.h"
#include "gsiDecl.h"
#include "dbStatic.h"
#include "dbLayoutDiff.h"
#include "dbWriter.h"
#include "dbGDS2Reader.h"
#include "dbOASISReader.h"
#include "dbGDS2Writer.h"
#include "dbOASISWriter.h"
#include "dbGDS2Reader.h"
#include <cstdio>
#include <unistd.h>
#if !defined(_WIN32)
# include <sys/ioctl.h>
#endif
#if defined(_WIN32)
# include <Windows.h>
#endif
#include <QDir>
#include <QRegExp>
#include <QTextCodec>
namespace ut
{
// --------------------------------------------------------------------------------------
ut::Registrar *ut::Registrar::ms_instance = 0;
static bool s_verbose_flag = false;
static bool s_debug_mode = false;
static bool s_continue_flag = false;
static pya::PythonInterpreter *sp_python_interpreter = 0;
static rba::RubyInterpreter *sp_ruby_interpreter = 0;
bool verbose ()
{
return s_verbose_flag;
}
pya::PythonInterpreter *python_interpreter ()
{
return sp_python_interpreter;
}
rba::RubyInterpreter *ruby_interpreter ()
{
return sp_ruby_interpreter;
}
bool is_debug_mode ()
{
return s_debug_mode;
}
std::string testsrc ()
{
const char *ts = getenv ("TESTSRC");
if (! ts) {
throw tl::Exception ("TESTSRC undefined");
}
return ts;
}
std::string testsrc_private ()
{
QDir d (QDir (tl::to_qstring (ut::testsrc ())).filePath (QString::fromUtf8 ("private")));
if (! d.exists ()) {
throw tl::CancelException ();
}
return tl::to_string (d.path ());
}
bool equals (double a, double b)
{
double m = fabs (0.5 * (a + b));
if (m < 1e-30) {
// resolution limit is 1e-30
return true;
} else {
double d = fabs (a - b);
// we consider two values equal for the purpose of unit tests if they have the
// same value within 1e-10 (0.00000001%).
return d < 1e-10 * m;
}
}
std::string replicate (const char *s, size_t n)
{
std::string res;
res.reserve (strlen (s) * n);
while (n > 0) {
res += s;
--n;
}
return res;
}
void print_error (const std::string &s)
{
tl::error << "ERROR: " << s;
}
// ------------------------------------------------
// tl::Channel implementations for redirecting the log output
const char *ANSI_RED = "\033[31;1m";
const char *ANSI_BLUE = "\033[34m";
const char *ANSI_GREEN = "\033[32m";
const char *ANSI_RESET = "\033[0m";
/**
* @brief Redirects the interpreter output to ut::print_stdout and ut::print_stderr
*/
class TestConsole
: public gsi::Console
{
public:
static TestConsole *instance ()
{
tl_assert (ms_instance != 0);
return ms_instance;
}
TestConsole (FILE *file, bool xml_format)
: m_file (file), m_xml_format (xml_format), m_col (0), m_max_col (250), m_columns (50), m_rows (0), m_is_tty (false)
{
ms_instance = this;
m_indent = 4;
m_is_tty = isatty (fileno (file)) && ! xml_format;
#if !defined(_WIN32)
if (m_is_tty) {
struct winsize ws;
ioctl (fileno (stdout), TIOCGWINSZ, &ws);
m_columns = std::max (0, (int) ws.ws_col);
m_rows = std::max (0, (int) ws.ws_row);
}
#endif
}
~TestConsole ()
{
if (ms_instance == this) {
ms_instance = 0;
}
}
int indent () const
{
return m_indent;
}
bool xml_format () const
{
return m_xml_format;
}
void write_str (const char *text, output_stream os)
{
if (os == OS_stderr) {
begin_error ();
basic_write (text);
end ();
} else {
basic_write (text);
}
}
void raw_write (const char *text)
{
fputs (text, m_file);
}
virtual void flush ()
{
fflush (m_file);
}
virtual bool is_tty ()
{
// NOTE: this assumes we are delivering to stdout
return m_is_tty;
}
virtual int columns ()
{
return std::max (m_columns - m_indent, 0);
}
virtual int rows ()
{
return m_rows;
}
int real_columns ()
{
return m_columns;
}
void begin_error ()
{
if (m_is_tty) {
fputs (ANSI_RED, m_file);
}
}
void begin_info ()
{
if (m_is_tty) {
fputs (ANSI_GREEN, m_file);
}
}
void begin_warn ()
{
if (m_is_tty) {
fputs (ANSI_BLUE, m_file);
}
}
void end ()
{
if (m_is_tty) {
fputs (ANSI_RESET, m_file);
}
}
void basic_write (const char *s)
{
if (m_xml_format) {
for (const char *cp = s; *cp; ++cp) {
if (*cp == '&') {
fputs ("&amp;", m_file);
} else if (*cp == '<') {
fputs ("&lt;", m_file);
} else if (*cp == '>') {
fputs ("&gt;", m_file);
} else {
fputc (*cp, m_file);
}
}
} else {
// line length limitation - this assumes we are always printing to the same terminal
// or we don't mix stderr/stdout.
const char *cp;
for (cp = s; *cp; ++cp) {
if (*cp == '\n' || *cp == '\r') {
m_col = 0;
fputc (*cp, m_file);
} else {
if (m_col == 0) {
for (int i = 0; i < m_indent; ++i) {
fputc (' ', m_file);
}
m_col = m_indent;
}
if (m_col > m_max_col) {
// ignore char
} else if (m_col == m_max_col) {
fputs (" ...", m_file);
++m_col;
} else if (*cp == '\033') {
// skip ANSI escape sequences (no increment of s_col)
const char *cpend = cp + 1;
if (*cpend == '[') {
++cpend;
while (*cpend && *cpend != 'm') {
++cpend;
}
if (*cpend) {
++cpend;
}
}
while (cp != cpend) {
fputc (*cp++, m_file);
}
--cp;
} else {
fputc (*cp, m_file);
++m_col;
}
}
}
}
}
private:
FILE *m_file;
bool m_xml_format;
int m_col;
int m_max_col;
int m_columns, m_rows;
bool m_is_tty;
int m_indent;
static TestConsole *ms_instance;
};
TestConsole *TestConsole::ms_instance = 0;
class InfoChannel : public tl::Channel
{
public:
InfoChannel (int verbosity)
: m_verbosity (verbosity)
{
// .. nothing yet ..
}
protected:
virtual void puts (const char *s)
{
if (tl::verbosity () >= m_verbosity) {
TestConsole::instance ()->basic_write (s);
}
}
virtual void endl ()
{
if (tl::verbosity () >= m_verbosity) {
TestConsole::instance ()->basic_write ("\n");
}
}
virtual void end ()
{
TestConsole::instance ()->flush ();
}
virtual void begin ()
{
// .. nothing yet ..
}
private:
int m_verbosity;
};
class WarningChannel : public tl::Channel
{
public:
WarningChannel ()
{
// .. nothing yet ..
}
protected:
virtual void puts (const char *s)
{
TestConsole::instance ()->basic_write (s);
}
virtual void endl ()
{
TestConsole::instance ()->basic_write ("\n");
}
virtual void end ()
{
TestConsole::instance ()->end ();
TestConsole::instance ()->flush ();
}
virtual void begin ()
{
TestConsole::instance ()->begin_warn ();
}
};
class ErrorChannel : public tl::Channel
{
public:
ErrorChannel ()
{
// .. nothing yet ..
}
protected:
virtual void puts (const char *s)
{
TestConsole::instance ()->basic_write (s);
}
virtual void endl ()
{
TestConsole::instance ()->basic_write ("\n");
}
virtual void end ()
{
TestConsole::instance ()->end ();
TestConsole::instance ()->flush ();
}
virtual void begin ()
{
TestConsole::instance ()->begin_error ();
}
};
void write_detailed_diff (std::ostream &os, const std::string &subject, const std::string &ref)
{
os << replicate (" ", TestConsole::instance ()->indent ()) << "Actual value is: " << tl::to_string (subject) << std::endl
<< replicate (" ", TestConsole::instance ()->indent ()) << "Reference value is: " << tl::to_string (ref) << std::endl
;
}
class CtrlChannel : public tl::Channel
{
public:
CtrlChannel (bool with_xml)
: m_with_xml (with_xml)
{
// .. nothing yet ..
}
protected:
virtual void puts (const char *s)
{
if (m_with_xml == TestConsole::instance ()->xml_format ()) {
TestConsole::instance ()->raw_write (s);
}
}
virtual void endl ()
{
if (m_with_xml == TestConsole::instance ()->xml_format ()) {
TestConsole::instance ()->raw_write ("\n");
}
}
virtual void end ()
{
if (m_with_xml == TestConsole::instance ()->xml_format ()) {
TestConsole::instance ()->end ();
TestConsole::instance ()->flush ();
}
}
virtual void begin ()
{
if (m_with_xml == TestConsole::instance ()->xml_format ()) {
TestConsole::instance ()->begin_info ();
}
}
private:
bool m_with_xml;
};
tl::LogTee noctrl (new CtrlChannel (false), true);
tl::LogTee ctrl (new CtrlChannel (true), true);
// --------------------------------------------------------------------------------------
// TestBase implementation
static QDir testtmp ()
{
// Ensures the test temp directory is present
const char *tt = getenv ("TESTTMP");
if (! tt) {
throw tl::Exception ("TESTTMP undefined");
}
return QDir (tl::to_qstring (tt));
}
TestBase::TestBase (const std::string &file, const std::string &name)
: m_cp_line (0), m_any_failed (false)
{
QFileInfo f (tl::to_qstring (file));
m_test = tl::to_string (f.baseName ()) + ":" + name;
m_testdir = tl::to_string (f.baseName ()) + "_" + name;
ut::Registrar::reg (this);
}
bool TestBase::do_test (const std::string & /*mode*/)
{
// Ensures the test temp directory is present
QDir dir (testtmp ());
QDir tmpdir (dir.absoluteFilePath (tl::to_qstring (m_testdir)));
if (tmpdir.exists () && ! tl::rm_dir_recursive (tmpdir.absolutePath ())) {
throw tl::Exception ("Unable to clean temporary dir: " + tl::to_string (tmpdir.absolutePath ()));
}
if (! dir.mkpath (tl::to_qstring (m_testdir))) {
throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (tmpdir.absolutePath ()));
}
dir.cd (tl::to_qstring (m_testdir));
m_testtmp = dir.absolutePath ();
static std::string testname_value;
static std::string testtmp_value;
putenv (const_cast<char *> ("TESTNAME="));
testname_value = std::string ("TESTNAME=") + m_test;
putenv (const_cast<char *> (testname_value.c_str ()));
putenv (const_cast<char *> ("TESTTMP_WITH_NAME="));
testtmp_value = std::string ("TESTTMP_WITH_NAME=") + m_testtmp.toUtf8().constData();
putenv (const_cast<char *> (testtmp_value.c_str ()));
reset_checkpoint ();
tl::Timer timer;
timer.start();
execute (this);
timer.stop();
m_testtmp.clear ();
ut::noctrl << "Time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)";
ut::ctrl << "<test-sub-times wall=\"" << timer.sec_wall () << "\" user=\"" << timer.sec_user () << "\" sys=\"" << timer.sec_sys () << "\"/>";
return (!m_any_failed);
}
std::string TestBase::tmp_file (const std::string &fn) const
{
tl_assert (! m_testtmp.isEmpty ());
QDir dir (m_testtmp);
return tl::to_string (dir.absoluteFilePath (tl::to_qstring (fn)));
}
/**
* @brief Recursively empties a directory
*/
static void empty_dir (QDir dir)
{
QStringList entries = dir.entryList (QDir::AllEntries | QDir::NoDotAndDotDot);
for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) {
QString epath = dir.absoluteFilePath (*e);
if (QFileInfo (epath).isDir ()) {
empty_dir (QDir (epath));
dir.rmdir (*e);
} else if (! dir.remove (*e)) {
throw tl::Exception ("Unable to remove file or directory: " + tl::to_string (dir.filePath (*e)));
}
}
}
void TestBase::remove_tmp_folder ()
{
// Ensures the test temp directory is present
QDir dir (testtmp ());
if (dir.cd (tl::to_qstring (m_test))) {
empty_dir (dir);
dir.cdUp ();
if (! dir.rmdir (tl::to_qstring (m_test))) {
throw tl::Exception ("Unable to remove directory: " + tl::to_string (dir.filePath (tl::to_qstring (m_test))));
}
}
}
void TestBase::checkpoint (const std::string &file, int line)
{
m_cp_file = file;
m_cp_line = line;
}
void TestBase::reset_checkpoint ()
{
m_cp_file = std::string ();
m_cp_line = 0;
}
void TestBase::raise (const std::string &file, int line, const std::string &msg)
{
std::ostringstream sstr;
sstr << file << ", line " << line << ": " << msg;
if (s_continue_flag) {
tl::error << sstr.str ();
m_any_failed = true;
} else {
throw ut::Exception (sstr.str ());
}
}
void TestBase::raise (const std::string &msg)
{
std::ostringstream sstr;
if (m_cp_line > 0) {
sstr << "(last checkpoint: " << m_cp_file << ", line " << m_cp_line << "): ";
}
sstr << msg;
if (s_continue_flag) {
tl::error << sstr.str ();
m_any_failed = true;
} else {
throw ut::Exception (sstr.str ());
}
}
void TestBase::compare_layouts (const db::Layout &layout, const std::string &au_file, NormalizationMode norm, db::Coord tolerance)
{
compare_layouts (layout, au_file, db::LayerMap (), true, norm, tolerance);
}
void TestBase::compare_layouts (const db::Layout &layout, const std::string &au_file, const db::LayerMap &lm, bool read_other_layers, NormalizationMode norm, db::Coord tolerance)
{
// normalize the layout by writing to GDS and reading from ..
// generate a "unique" name ...
unsigned int hash = 0;
for (const char *cp = au_file.c_str (); *cp; ++cp) {
hash = (hash << 4) ^ (hash >> 4) ^ ((unsigned int) *cp);
}
std::string tmp_file;
if (norm == WriteGDS2) {
tmp_file = ut::TestBase::tmp_file (tl::sprintf ("tmp_%x.gds", hash));
tl::OutputStream stream (tmp_file.c_str ());
db::GDS2Writer writer;
db::SaveLayoutOptions options;
writer.write (const_cast<db::Layout &> (layout), stream, options);
} else {
tmp_file = ut::TestBase::tmp_file (tl::sprintf ("tmp_%x.oas", hash));
tl::OutputStream stream (tmp_file.c_str ());
db::OASISWriter writer;
db::SaveLayoutOptions options;
writer.write (const_cast<db::Layout &> (layout), stream, options);
}
const db::Layout *subject = 0;
db::Layout layout2;
if (norm != NoNormalization) {
// read all layers from the original layout, so the layer table is the same
for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) {
layout2.insert_layer ((*l).first, *(*l).second);
}
tl::InputStream stream (tmp_file);
db::Reader reader (stream);
reader.read (layout2);
subject = &layout2;
} else {
subject = &layout;
}
bool equal = false;
bool any = false;
int n = 0;
for ( ; ! equal; ++n) {
db::Layout layout_au;
// read all layers from the original layout, so the layer table is the same
for (db::Layout::layer_iterator l = layout.begin_layers (); l != layout.end_layers (); ++l) {
layout_au.insert_layer ((*l).first, *(*l).second);
}
db::LoadLayoutOptions options;
options.get_options<db::CommonReaderOptions> ().layer_map = lm;
options.get_options<db::CommonReaderOptions> ().create_other_layers = read_other_layers;
std::string fn = au_file;
if (n > 0) {
fn += tl::sprintf (".%d", n);
}
if (QFileInfo (tl::to_qstring (fn)).exists ()) {
if (n == 1 && any) {
throw tl::Exception (tl::sprintf ("Inconsistent reference variants for %s: there can be either variants (.1,.2,... suffix) or a single file (without suffix)", au_file));
}
any = true;
tl::InputStream stream (fn);
db::Reader reader (stream);
reader.read (layout_au, options);
equal = db::compare_layouts (*subject, layout_au, (n > 0 ? db::layout_diff::f_silent : db::layout_diff::f_verbose) | db::layout_diff::f_flatten_array_insts /*| db::layout_diff::f_no_text_details | db::layout_diff::f_no_text_orientation*/, tolerance, 100 /*max diff lines*/);
if (equal && n > 0) {
tl::info << tl::sprintf ("Found match on golden reference variant %s", fn);
}
} else if (n > 0) {
if (! any) {
tl::warn << tl::sprintf ("No golden data found (%s)", au_file);
}
break;
}
}
if (! equal) {
raise (tl::sprintf ("Compare failed - see %s vs %s\n", tmp_file, au_file + (n > 1 ? " and variants" : "")));
}
}
static int main_cont (int argc, char **argv);
int
main (int argc, char **argv)
{
int ret = rba::RubyInterpreter::initialize (argc, argv, &main_cont);
// NOTE: this needs to happen after the Ruby interpreter went down since otherwise the GC will
// access objects that are already cleaned up.
tl::StaticObjects::cleanup ();
return ret;
}
int
main_cont (int argc, char **argv)
{
pya::PythonInterpreter::initialize ();
gsi::initialize_external ();
int ac = 2;
static char av0[] = "unit_test";
static char av1[] = "-z"; // don't show main window
static char av2[] = "-nc"; // No configuration file
static char av3[] = "-rx"; // No mplicit macros
char *av[] = { av0, av1, av2, av3, 0 };
lay::Application app (ac, av, false);
#if QT_VERSION < 0x050000
QTextCodec::setCodecForTr (QTextCodec::codecForName ("utf8"));
#endif
bool editable = true, non_editable = true;
bool exclude = false;
bool gsi_coverage = false;
bool gsi_coverage_selected = false;
std::set<std::string> class_names;
std::vector<std::string> test_list;
std::vector<std::string> exclude_test_list;
bool xml_format = false;
for (int i = 1; i < argc; ++i) {
std::string a = argv[i];
if (a == "-h") {
std::cout << "unit_test <Options> <Test list>" << std::endl
<< "Options:" << std::endl
<< " -a XML output format" << std::endl
<< " -l List tests and exit" << std::endl
<< " -e Editable mode only" << std::endl
<< " -ne Non-editable mode only" << std::endl
<< " -c Continue on error" << std::endl
<< " -v Verbose mode" << std::endl
<< " -d debug mode (stop on error, indicate fix instructions)" << std::endl
<< " -g GSI coverage mode - print GSI methods that have not been called" << std::endl
<< " -gg <class> GSI coverage mode, confined to this class (can be given multiple times)" << std::endl
<< " -x Exclude following tests" << std::endl
<< "Test list: list of match strings selecting some tests (default: all)" << std::endl;
return 0;
} else if (a == "-l") {
std::cout << "List of installed tests:" << std::endl;
for (std::vector<ut::TestBase *>::const_iterator i = ut::Registrar::instance()->tests ().begin (); i != ut::Registrar::instance()->tests ().end (); ++i) {
std::cout << " " << (*i)->name () << std::endl;
}
return 0;
} else if (a == "-a") {
xml_format = true;
} else if (a == "-g") {
gsi_coverage = true;
} else if (a == "-gg") {
gsi_coverage = true;
gsi_coverage_selected = true;
if (i + 1 < argc) {
++i;
class_names.insert (argv [i]);
}
} else if (a == "-e") {
non_editable = false;
editable = true;
} else if (a == "-ne") {
non_editable = true;
editable = false;
} else if (a == "-c") {
ut::s_continue_flag = true;
} else if (a == "-d") {
ut::s_debug_mode = true;
} else if (a == "-v") {
ut::s_verbose_flag = true;
} else if (a == "-x") {
exclude = true;
} else {
if (exclude) {
exclude_test_list.push_back (a);
} else {
test_list.push_back (a);
}
}
}
ut::TestConsole console (stdout, xml_format);
// redirect the log channels
tl::warn.clear ();
tl::warn.add (new ut::WarningChannel (), true);
tl::info.clear ();
tl::info.add (new ut::InfoChannel (0), true);
tl::log.clear ();
tl::log.add (new ut::InfoChannel (10), true);
tl::error.clear ();
tl::error.add (new ut::ErrorChannel (), true);
int result = 0;
try {
tl::Timer grand_timer;
grand_timer.start ();
ut::sp_ruby_interpreter = dynamic_cast <rba::RubyInterpreter *> (&app.ruby_interpreter ());
ut::sp_python_interpreter = dynamic_cast <pya::PythonInterpreter *> (&app.python_interpreter ());
if (ut::sp_ruby_interpreter) {
ut::sp_ruby_interpreter->push_console (&console);
}
if (ut::sp_python_interpreter) {
ut::sp_python_interpreter->push_console (&console);
}
ut::ctrl << "<tests>";
ut::noctrl << replicate ("=", console.real_columns ());
ut::noctrl << "Entering KLayout test suite";
tl::info << "TESTSRC=" << ut::testsrc ();
tl::info << "TESTTMP=" << tl::to_string (ut::testtmp ().absolutePath ());
const std::vector<ut::TestBase *> *selected_tests = 0;
std::vector<ut::TestBase *> subset;
if (! test_list.empty ()) {
selected_tests = &subset;
tl::info << "Selected tests:";
for (std::vector<ut::TestBase *>::const_iterator i = ut::Registrar::instance()->tests ().begin (); i != ut::Registrar::instance()->tests ().end (); ++i) {
bool exclude = false;
for (std::vector<std::string>::const_iterator m = exclude_test_list.begin (); m != exclude_test_list.end (); ++m) {
QRegExp re (tl::to_qstring (*m), Qt::CaseInsensitive, QRegExp::Wildcard);
if (re.indexIn (tl::to_qstring ((*i)->name ())) >= 0) {
exclude = true;
break;
}
}
for (std::vector<std::string>::const_iterator m = test_list.begin (); !exclude && m != test_list.end (); ++m) {
QRegExp re (tl::to_qstring (*m), Qt::CaseInsensitive, QRegExp::Wildcard);
if (re.indexIn (tl::to_qstring ((*i)->name ())) >= 0) {
tl::info << " " << (*i)->name ();
subset.push_back (*i);
break;
}
}
}
} else {
selected_tests = &ut::Registrar::instance()->tests ();
}
ut::s_verbose_flag = false;
int failed_ne = 0, failed_e = 0;
std::vector <ut::TestBase *> failed_tests_e, failed_tests_ne;
int skipped_ne = 0, skipped_e = 0;
std::vector <ut::TestBase *> skipped_tests_e, skipped_tests_ne;
for (int e = 0; e < 2; ++e) {
if ((non_editable && e == 0) || (editable && e == 1)) {
std::string mode (e == 0 ? "non-editable" : "editable");
ut::noctrl << replicate ("=", console.real_columns ());
ut::noctrl << "Running tests in " << mode << " mode ...";
app.set_editable (e != 0);
int failed = 0;
std::vector <ut::TestBase *> failed_tests;
int skipped = 0;
std::vector <ut::TestBase *> skipped_tests;
tl::Timer timer;
timer.start ();
try {
failed = 0;
failed_tests.clear ();
skipped = 0;
skipped_tests.clear ();
for (std::vector <ut::TestBase *>::const_iterator t = selected_tests->begin (); t != selected_tests->end (); ++t) {
(*t)->remove_tmp_folder ();
}
for (std::vector <ut::TestBase *>::const_iterator t = selected_tests->begin (); t != selected_tests->end (); ++t) {
ut::ctrl << "<test-sub name=\"" << (*t)->name () << "\" mode=\"" << mode << "\">";
ut::noctrl << replicate ("-", TestConsole::instance ()->real_columns ());
ut::noctrl << "Running " << (*t)->name ();
try {
if (! (*t)->do_test (mode)) {
ut::ctrl << "<test-sub-result status=\"error\">";
tl::error << "Test " << (*t)->name () << " failed (continued mode - see previous messages)";
ut::ctrl << "</test-sub-result>";
failed_tests.push_back (*t);
++failed;
} else {
ut::ctrl << "<test-sub-result status=\"success\"/>";
}
} catch (tl::CancelException &) {
ut::ctrl << "<test-sub-result status=\"skipped\"/>";
tl::error << "Test " << (*t)->name () << " skipped";
skipped_tests.push_back (*t);
++skipped;
} catch (tl::Exception &ex) {
ut::ctrl << "<test-sub-result status=\"error\">";
tl::error << "Test " << (*t)->name () << " failed:";
tl::info << ex.msg ();
ut::ctrl << "</test-sub-result>";
failed_tests.push_back (*t);
++failed;
}
ut::ctrl << "</test-sub>";
}
} catch (tl::Exception &ex) {
tl::error << "Caught tl::exception: " << ex.msg ();
failed = 1;
} catch (std::exception &ex) {
tl::error << "Caught std::exception: " << std::string (ex.what ());
failed = 1;
} catch (...) {
tl::error << "Caught unspecific exception";
failed = 1;
}
timer.stop ();
ut::ctrl << "<summary mode=\"" << mode << "\">";
ut::noctrl << replicate ("=", console.real_columns ());
ut::noctrl << "Summary";
if (skipped > 0) {
if (e == 0) {
skipped_tests_ne = skipped_tests;
skipped_ne = skipped;
} else {
skipped_tests_e = skipped_tests;
skipped_e = skipped;
}
tl::warn << skipped << " test(s) skipped";
}
if (failed > 0) {
if (e == 0) {
failed_tests_ne = failed_tests;
failed_ne = failed;
} else {
failed_tests_e = failed_tests;
failed_e = failed;
}
tl::warn << failed << " test(s) failed";
} else {
tl::info << "All tests passed in " << mode << " mode.";
}
ut::ctrl << "</summary>";
ut::noctrl << "Total time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)";
ut::ctrl << "<summary-times mode=\"" << mode << "\" wall=\"" << timer.sec_wall () << "\" user=\"" << timer.sec_user () << "\" sys=\"" << timer.sec_sys () << "\"/>";
}
}
grand_timer.stop ();
// GSI diagnostics: print all methods that have not been called
if (gsi_coverage) {
ut::noctrl << replicate ("=", console.real_columns ());
ut::noctrl << "GSI coverage test";
ut::ctrl << "<gsi-coverage>";
bool first = true;
for (gsi::ClassBase::class_iterator c = gsi::ClassBase::begin_classes (); c != gsi::ClassBase::end_classes (); ++c) {
if (gsi_coverage_selected && class_names.find (c->name ()) == class_names.end ()) {
continue;
}
bool first_of_class = true;
for (gsi::ClassBase::method_iterator m = c->begin_methods (); m != c->end_methods (); ++m) {
if (!dynamic_cast<const gsi::SpecialMethod *> (*m) && !(*m)->was_called ()) {
if (first) {
first = false;
tl::warn << "GSI coverage test failed - the following methods were not called:";
}
if (first_of_class) {
tl::warn << replicate (" ", console.indent ()) << "Class " << c->name ();
first_of_class = false;
}
tl::warn << replicate (" ", console.indent () * 2) << (*m)->to_string ();
}
}
}
if (first) {
tl::info << "GSI coverage test passed.";
}
ut::ctrl << "</gsi-coverage>";
}
ut::noctrl << ut::replicate ("=", console.real_columns ());
ut::noctrl << "Grand Summary";
ut::ctrl << "<grand-summary>";
if (skipped_e + skipped_ne > 0) {
if (non_editable) {
tl::warn << "Skipped in non-editable mode";
for (std::vector <ut::TestBase *>::const_iterator f = skipped_tests_ne.begin (); f != skipped_tests_ne.end (); ++f) {
tl::warn << replicate (" ", console.indent ()) << (*f)->name ();
}
}
if (editable) {
tl::warn << "Skipped in editable mode";
for (std::vector <ut::TestBase *>::const_iterator f = skipped_tests_e.begin (); f != skipped_tests_e.end (); ++f) {
tl::warn << replicate (" ", console.indent ()) << (*f)->name ();
}
}
tl::warn << tl::to_string (result) << " test(s) skipped";
}
result = failed_e + failed_ne;
if (result > 0) {
if (non_editable) {
tl::warn << "Failed in non-editable mode";
for (std::vector <ut::TestBase *>::const_iterator f = failed_tests_ne.begin (); f != failed_tests_ne.end (); ++f) {
tl::warn << replicate (" ", console.indent ()) << (*f)->name ();
}
}
if (editable) {
tl::warn << "Failed in editable mode";
for (std::vector <ut::TestBase *>::const_iterator f = failed_tests_e.begin (); f != failed_tests_e.end (); ++f) {
tl::warn << replicate (" ", console.indent ()) << (*f)->name ();
}
}
tl::warn << tl::to_string (result) << " test(s) failed";
} else {
tl::info << "All tests passed.";
}
ut::ctrl << "</grand-summary>";
ut::noctrl << "Grand total time: " << grand_timer.sec_wall () << "s (wall) " << grand_timer.sec_user () << "s (user) " << grand_timer.sec_sys () << "s (sys)";
ut::ctrl << "<grand-summary-times wall=\"" << grand_timer.sec_wall () << "\" user=\"" << grand_timer.sec_user () << "\" sys=\"" << grand_timer.sec_sys () << "\"/>";
if (ut::sp_ruby_interpreter) {
ut::sp_ruby_interpreter->remove_console (&console);
}
if (ut::sp_python_interpreter) {
ut::sp_python_interpreter->remove_console (&console);
}
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
result = -1;
} catch (std::exception &ex) {
tl::error << ex.what ();
result = -1;
} catch (...) {
tl::error << "Unspecific exception";
result = -1;
}
ut::ctrl << "</tests>";
return result;
}
} // namespace ut