/* 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 #include #if !defined(_WIN32) # include #endif #if defined(_WIN32) # include #endif #include #include #include 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 ("&", m_file); } else if (*cp == '<') { fputs ("<", m_file); } else if (*cp == '>') { fputs (">", 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 ("TESTNAME=")); testname_value = std::string ("TESTNAME=") + m_test; putenv (const_cast (testname_value.c_str ())); putenv (const_cast ("TESTTMP_WITH_NAME=")); testtmp_value = std::string ("TESTTMP_WITH_NAME=") + m_testtmp.toUtf8().constData(); putenv (const_cast (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 << ""; 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 (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 (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 ().layer_map = lm; options.get_options ().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 class_names; std::vector test_list; std::vector 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 " << 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 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::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 (&app.ruby_interpreter ()); ut::sp_python_interpreter = dynamic_cast (&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 << ""; 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 *selected_tests = 0; std::vector subset; if (! test_list.empty ()) { selected_tests = ⊂ tl::info << "Selected tests:"; for (std::vector::const_iterator i = ut::Registrar::instance()->tests ().begin (); i != ut::Registrar::instance()->tests ().end (); ++i) { bool exclude = false; for (std::vector::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::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 failed_tests_e, failed_tests_ne; int skipped_ne = 0, skipped_e = 0; std::vector 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 failed_tests; int skipped = 0; std::vector skipped_tests; tl::Timer timer; timer.start (); try { failed = 0; failed_tests.clear (); skipped = 0; skipped_tests.clear (); for (std::vector ::const_iterator t = selected_tests->begin (); t != selected_tests->end (); ++t) { (*t)->remove_tmp_folder (); } for (std::vector ::const_iterator t = selected_tests->begin (); t != selected_tests->end (); ++t) { ut::ctrl << "name () << "\" mode=\"" << mode << "\">"; ut::noctrl << replicate ("-", TestConsole::instance ()->real_columns ()); ut::noctrl << "Running " << (*t)->name (); try { if (! (*t)->do_test (mode)) { ut::ctrl << ""; tl::error << "Test " << (*t)->name () << " failed (continued mode - see previous messages)"; ut::ctrl << ""; failed_tests.push_back (*t); ++failed; } else { ut::ctrl << ""; } } catch (tl::CancelException &) { ut::ctrl << ""; tl::error << "Test " << (*t)->name () << " skipped"; skipped_tests.push_back (*t); ++skipped; } catch (tl::Exception &ex) { ut::ctrl << ""; tl::error << "Test " << (*t)->name () << " failed:"; tl::info << ex.msg (); ut::ctrl << ""; failed_tests.push_back (*t); ++failed; } ut::ctrl << ""; } } 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 << ""; 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 << ""; ut::noctrl << "Total time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)"; ut::ctrl << ""; } } 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 << ""; 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 (*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 << ""; } ut::noctrl << ut::replicate ("=", console.real_columns ()); ut::noctrl << "Grand Summary"; ut::ctrl << ""; if (skipped_e + skipped_ne > 0) { if (non_editable) { tl::warn << "Skipped in non-editable mode"; for (std::vector ::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 ::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 ::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 ::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 << ""; 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 << ""; 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 << ""; return result; } } // namespace ut