mirror of https://github.com/KLayout/klayout.git
552 lines
18 KiB
C++
552 lines
18 KiB
C++
|
|
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2018 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 "utTestConsole.h"
|
|
|
|
#include "tlUnitTest.h"
|
|
#include "tlStaticObjects.h"
|
|
#include "tlTimer.h"
|
|
#include "tlCommandLineParser.h"
|
|
#include "layApplication.h"
|
|
#include "laySystemPaths.h"
|
|
#include "layVersion.h"
|
|
#include "rba.h"
|
|
#include "pya.h"
|
|
#include "gsiDecl.h"
|
|
#include "gsiExternalMain.h"
|
|
|
|
#include "version.h"
|
|
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QTextCodec>
|
|
|
|
#if !defined(_WIN32)
|
|
# include <dlfcn.h>
|
|
#endif
|
|
#if defined(_WIN32)
|
|
# include <Windows.h>
|
|
#endif
|
|
|
|
// required to force linking of the "ext", "lib" and "drc" module
|
|
#include "extForceLink.h"
|
|
#include "libForceLink.h"
|
|
#if defined(HAVE_RUBY)
|
|
#include "drcForceLink.h"
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
static bool
|
|
run_test (tl::TestBase *t, bool editable, bool slow, int repeat)
|
|
{
|
|
for (int i = 0; i < repeat; ++i) {
|
|
if (repeat > 0) {
|
|
ut::noctrl << "Repeat iteration " << i + 1 << " of " << repeat;
|
|
}
|
|
if (! t->do_test (editable, slow)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
run_tests (const std::vector<tl::TestBase *> &selected_tests, bool editable, bool non_editable, bool slow, int repeat, lay::ApplicationBase &app, bool gsi_coverage, const std::vector<std::string> &class_names_vector)
|
|
{
|
|
std::set<std::string> class_names;
|
|
class_names.insert (class_names_vector.begin (), class_names_vector.end ());
|
|
|
|
tl::Timer grand_timer;
|
|
grand_timer.start ();
|
|
|
|
int failed_ne = 0, failed_e = 0;
|
|
std::vector <tl::TestBase *> failed_tests_e, failed_tests_ne;
|
|
int skipped_ne = 0, skipped_e = 0;
|
|
std::vector <tl::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::ctrl << "<testsuite name=\"ut-runner-" << mode << "\">";
|
|
|
|
ut::noctrl << tl::replicate ("=", ut::TestConsole::instance ()->real_columns ());
|
|
ut::noctrl << "Running tests in " << mode << " mode ...";
|
|
app.set_editable (e != 0);
|
|
|
|
int failed = 0;
|
|
std::vector <tl::TestBase *> failed_tests;
|
|
int skipped = 0;
|
|
std::vector <tl::TestBase *> skipped_tests;
|
|
|
|
tl::Timer timer;
|
|
|
|
timer.start ();
|
|
|
|
try {
|
|
|
|
failed = 0;
|
|
failed_tests.clear ();
|
|
skipped = 0;
|
|
skipped_tests.clear ();
|
|
|
|
for (std::vector <tl::TestBase *>::const_iterator t = selected_tests.begin (); t != selected_tests.end (); ++t) {
|
|
(*t)->remove_tmp_folder ();
|
|
}
|
|
|
|
for (std::vector <tl::TestBase *>::const_iterator t = selected_tests.begin (); t != selected_tests.end (); ++t) {
|
|
|
|
ut::ctrl << "<testcase name=\"" << (*t)->name () << "\">";
|
|
|
|
ut::noctrl << tl::replicate ("-", ut::TestConsole::instance ()->real_columns ());
|
|
ut::noctrl << "Running " << (*t)->name ();
|
|
|
|
try {
|
|
|
|
ut::ctrl << "<system-out>";
|
|
|
|
tl::Timer timer;
|
|
timer.start();
|
|
|
|
if (! run_test (*t, e != 0, slow, repeat)) {
|
|
|
|
ut::ctrl << "</system-out>";
|
|
|
|
ut::ctrl << "<error message=\"" << "Test " << tl::escaped_to_html ((*t)->name (), false) << " failed (continued mode - see previous messages)" << "\"/>";
|
|
tl::error << "Test " << (*t)->name () << " failed (continued mode - see previous messages)";
|
|
|
|
failed_tests.push_back (*t);
|
|
++failed;
|
|
|
|
} else {
|
|
ut::ctrl << "</system-out>";
|
|
}
|
|
|
|
timer.stop();
|
|
|
|
ut::noctrl << "Time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)";
|
|
ut::ctrl << "<x-testcase-times wall=\"" << timer.sec_wall () << "\" user=\"" << timer.sec_user () << "\" sys=\"" << timer.sec_sys () << "\"/>";
|
|
|
|
} catch (tl::CancelException &) {
|
|
|
|
ut::ctrl << "</system-out>";
|
|
ut::ctrl << "<skipped/>";
|
|
tl::error << "Test " << (*t)->name () << " skipped";
|
|
|
|
skipped_tests.push_back (*t);
|
|
++skipped;
|
|
|
|
} catch (tl::Exception &ex) {
|
|
|
|
ut::ctrl << "</system-out>";
|
|
ut::ctrl << "<failure message=\"" << tl::escaped_to_html (ex.msg (), false) << "\"/>";
|
|
tl::error << "Test " << (*t)->name () << " failed:";
|
|
tl::info << ex.msg ();
|
|
|
|
failed_tests.push_back (*t);
|
|
++failed;
|
|
|
|
}
|
|
|
|
ut::ctrl << "</testcase>";
|
|
|
|
}
|
|
|
|
} 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 << "<x-summary mode=\"" << mode << "\">";
|
|
|
|
ut::noctrl << tl::replicate ("=", ut::TestConsole::instance ()->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 << "</x-summary>";
|
|
|
|
ut::noctrl << "Total time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)";
|
|
ut::ctrl << "<x-summary-times mode=\"" << mode << "\" wall=\"" << timer.sec_wall () << "\" user=\"" << timer.sec_user () << "\" sys=\"" << timer.sec_sys () << "\"/>";
|
|
|
|
ut::ctrl << "</testsuite>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
grand_timer.stop ();
|
|
|
|
// GSI diagnostics: print all methods that have not been called
|
|
if (gsi_coverage) {
|
|
|
|
ut::noctrl << tl::replicate ("=", ut::TestConsole::instance ()->real_columns ());
|
|
ut::noctrl << "GSI coverage test";
|
|
|
|
ut::ctrl << "<x-gsi-coverage>";
|
|
|
|
bool first = true;
|
|
for (gsi::ClassBase::class_iterator c = gsi::ClassBase::begin_classes (); c != gsi::ClassBase::end_classes (); ++c) {
|
|
|
|
if (gsi_coverage && !class_names.empty () && 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 << tl::replicate (" ", tl::indent ()) << "Class " << c->name ();
|
|
first_of_class = false;
|
|
}
|
|
tl::warn << tl::replicate (" ", tl::indent () * 2) << (*m)->to_string ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (first) {
|
|
tl::info << "GSI coverage test passed.";
|
|
}
|
|
|
|
ut::ctrl << "</x-gsi-coverage>";
|
|
|
|
}
|
|
|
|
ut::noctrl << tl::replicate ("=", ut::TestConsole::instance ()->real_columns ());
|
|
ut::noctrl << "Grand Summary";
|
|
|
|
ut::ctrl << "<x-grand-summary>";
|
|
|
|
if (skipped_e + skipped_ne > 0) {
|
|
if (non_editable) {
|
|
tl::warn << "Skipped in non-editable mode";
|
|
for (std::vector <tl::TestBase *>::const_iterator f = skipped_tests_ne.begin (); f != skipped_tests_ne.end (); ++f) {
|
|
tl::warn << tl::replicate (" ", tl::indent ()) << (*f)->name ();
|
|
}
|
|
}
|
|
if (editable) {
|
|
tl::warn << "Skipped in editable mode";
|
|
for (std::vector <tl::TestBase *>::const_iterator f = skipped_tests_e.begin (); f != skipped_tests_e.end (); ++f) {
|
|
tl::warn << tl::replicate (" ", tl::indent ()) << (*f)->name ();
|
|
}
|
|
}
|
|
tl::warn << tl::to_string (skipped_e + skipped_ne) << " test(s) skipped";
|
|
}
|
|
|
|
int result = failed_e + failed_ne;
|
|
if (result > 0) {
|
|
if (non_editable) {
|
|
tl::warn << "Failed in non-editable mode";
|
|
for (std::vector <tl::TestBase *>::const_iterator f = failed_tests_ne.begin (); f != failed_tests_ne.end (); ++f) {
|
|
tl::warn << tl::replicate (" ", tl::indent ()) << (*f)->name ();
|
|
}
|
|
}
|
|
if (editable) {
|
|
tl::warn << "Failed in editable mode";
|
|
for (std::vector <tl::TestBase *>::const_iterator f = failed_tests_e.begin (); f != failed_tests_e.end (); ++f) {
|
|
tl::warn << tl::replicate (" ", tl::indent ()) << (*f)->name ();
|
|
}
|
|
}
|
|
tl::warn << tl::to_string (result) << " test(s) failed";
|
|
} else {
|
|
tl::info << "All tests passed.";
|
|
}
|
|
|
|
ut::ctrl << "</x-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 << "<x-grand-summary-times wall=\"" << grand_timer.sec_wall () << "\" user=\"" << grand_timer.sec_user () << "\" sys=\"" << grand_timer.sec_sys () << "\"/>";
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
main_cont (int &argc, char **argv)
|
|
{
|
|
// install the version strings
|
|
lay::Version::set_exe_name (prg_exe_name);
|
|
lay::Version::set_name (prg_name);
|
|
lay::Version::set_version (prg_version);
|
|
|
|
std::string subversion (prg_date);
|
|
subversion += " r";
|
|
subversion += prg_rev;
|
|
lay::Version::set_subversion (subversion.c_str ());
|
|
|
|
int result = 0;
|
|
|
|
ut::TestConsole console (stdout);
|
|
|
|
try {
|
|
|
|
pya::PythonInterpreter::initialize ();
|
|
gsi::initialize_external ();
|
|
|
|
// Search and initialize plugin unit tests
|
|
|
|
QStringList name_filters;
|
|
name_filters << QString::fromUtf8 ("*.ut");
|
|
|
|
QDir inst_dir (tl::to_qstring (lay::get_inst_path ()));
|
|
QStringList inst_modules = inst_dir.entryList (name_filters);
|
|
inst_modules.sort ();
|
|
|
|
for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
|
|
|
|
QFileInfo ut_file (inst_dir.path (), *im);
|
|
if (ut_file.exists () && ut_file.isReadable ()) {
|
|
|
|
std::string pp = tl::to_string (ut_file.absoluteFilePath ());
|
|
tl::log << "Loading unit tests " << pp;
|
|
|
|
// NOTE: since we are using a different suffix ("*.ut"), we can't use QLibrary.
|
|
#ifdef _WIN32
|
|
// there is no "dlopen" on mingw, so we need to emulate it.
|
|
HINSTANCE handle = LoadLibraryW ((const wchar_t *) tl::to_qstring (pp).constData ());
|
|
if (! handle) {
|
|
throw tl::Exception (tl::sprintf ("Unable to load plugin tests: %s with error message: %s", pp.c_str (), GetLastError ()));
|
|
}
|
|
#else
|
|
void *handle;
|
|
handle = dlopen (tl::string_to_system (pp).c_str (), RTLD_LAZY);
|
|
if (! handle) {
|
|
throw tl::Exception (tl::sprintf ("Unable to load plugin tests: %s", pp.c_str ()));
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (! tl::TestRegistrar::instance()) {
|
|
throw tl::Exception ("No test libraries found - make sure, the *.ut files are next to the ut_runner executable.");
|
|
}
|
|
|
|
// No side effects
|
|
lay::set_klayout_path (std::vector<std::string> ());
|
|
|
|
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 };
|
|
int ac = sizeof (av) / sizeof (av[0]) - 1;
|
|
lay::GuiApplication app (ac, av);
|
|
|
|
app.ruby_interpreter ().push_console (&console);
|
|
app.python_interpreter ().push_console (&console);
|
|
|
|
app.autorun ();
|
|
|
|
#if QT_VERSION < 0x050000
|
|
QTextCodec::setCodecForTr (QTextCodec::codecForName ("utf8"));
|
|
#endif
|
|
|
|
bool editable = false, non_editable = false;
|
|
bool gsi_coverage = false;
|
|
std::vector<std::string> class_names;
|
|
std::vector<std::string> test_list;
|
|
std::vector<std::string> exclude_test_list;
|
|
|
|
bool xml_format = false;
|
|
bool list_tests = false;
|
|
bool slow = false;
|
|
bool verbose = false;
|
|
bool debug_mode = false;
|
|
bool continue_flag = false;
|
|
int repeat = 1;
|
|
|
|
tl::CommandLineOptions cmd;
|
|
cmd << tl::arg ("-a", &xml_format, "Provide XML output format (JUnit format)")
|
|
<< tl::arg ("-l", &list_tests, "Lists tests and exits")
|
|
<< tl::arg ("-e", &editable, "Uses editable mode")
|
|
<< tl::arg ("-ne", &non_editable, "Uses non-editable mode")
|
|
<< tl::arg ("-c", &continue_flag, "Continues after an error")
|
|
<< tl::arg ("-i", &debug_mode, "Uses debug mode",
|
|
"In debug mode, execution stops after an error and if possible, fix instructions are "
|
|
"printed."
|
|
)
|
|
<< tl::arg ("-s", &slow, "Includes slow (long runner) tests")
|
|
<< tl::arg ("-v", &verbose, "Provides verbose output")
|
|
<< tl::arg ("-g", &gsi_coverage, "Produces a GSI test coverage statistics")
|
|
<< tl::arg ("-r=n", &repeat, "Repeat the tests n times each")
|
|
<< tl::arg ("*-gg=class", &class_names, "Produces a specific GDS coverage statistics",
|
|
"With this specification, coverage will be printed for this specific class. "
|
|
"This option can be used multiple times to add more classes."
|
|
)
|
|
<< tl::arg ("-x=test", &exclude_test_list, "Exclude the following tests",
|
|
"This option can be given multiple times or with a comma-separated list "
|
|
"of pattern. Test tests matching one of the exclude pattern "
|
|
"are not executed."
|
|
)
|
|
<< tl::arg ("?*test", &test_list, "The pattern for the tests to execute")
|
|
;
|
|
|
|
cmd.brief ("The runner executable for execution of the unit tests");
|
|
|
|
cmd.parse (argc, argv);
|
|
|
|
if (!editable && !non_editable) {
|
|
editable = non_editable = true;
|
|
}
|
|
|
|
if (!class_names.empty ()) {
|
|
gsi_coverage = true;
|
|
}
|
|
|
|
if (list_tests) {
|
|
tl::info << "List of installed tests:";
|
|
for (std::vector<tl::TestBase *>::const_iterator i = tl::TestRegistrar::instance()->tests ().begin (); i != tl::TestRegistrar::instance()->tests ().end (); ++i) {
|
|
tl::info << " " << (*i)->name ();
|
|
}
|
|
throw tl::CancelException ();
|
|
}
|
|
|
|
tl::set_verbose (verbose);
|
|
tl::set_xml_format (xml_format);
|
|
tl::set_continue_flag (continue_flag);
|
|
tl::set_debug_mode (debug_mode);
|
|
|
|
try {
|
|
|
|
ut::ctrl << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
|
|
ut::ctrl << "<testsuites>";
|
|
|
|
ut::noctrl << tl::replicate ("=", console.real_columns ());
|
|
ut::noctrl << "Entering KLayout test suite";
|
|
|
|
ut::noctrl << "TESTSRC=" << tl::testsrc ();
|
|
ut::noctrl << "TESTTMP=" << tl::to_string (QDir (tl::to_qstring (tl::testtmp ())).absolutePath ());
|
|
|
|
const std::vector<tl::TestBase *> *selected_tests = 0;
|
|
std::vector<tl::TestBase *> subset;
|
|
if (! test_list.empty ()) {
|
|
|
|
selected_tests = ⊂
|
|
ut::noctrl << "Selected tests:";
|
|
|
|
for (std::vector<tl::TestBase *>::const_iterator i = tl::TestRegistrar::instance()->tests ().begin (); i != tl::TestRegistrar::instance()->tests ().end (); ++i) {
|
|
|
|
bool exclude = false;
|
|
|
|
for (std::vector<std::string>::const_iterator m = exclude_test_list.begin (); m != exclude_test_list.end () && !exclude; ++m) {
|
|
QRegExp re (tl::to_qstring (*m), Qt::CaseInsensitive, QRegExp::Wildcard);
|
|
if (re.indexIn (tl::to_qstring ((*i)->name ())) == 0) {
|
|
exclude = true;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
ut::noctrl << " " << (*i)->name ();
|
|
subset.push_back (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
selected_tests = &tl::TestRegistrar::instance()->tests ();
|
|
}
|
|
|
|
result = run_tests (*selected_tests, editable, non_editable, slow, repeat, app, gsi_coverage, class_names);
|
|
|
|
ut::ctrl << "</testsuites>";
|
|
|
|
} catch (...) {
|
|
ut::ctrl << "</testsuites>";
|
|
throw;
|
|
}
|
|
|
|
} catch (tl::CancelException &) {
|
|
result = 0;
|
|
} 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;
|
|
}
|
|
|
|
return result;
|
|
}
|