mirror of https://github.com/KLayout/klayout.git
757 lines
23 KiB
C++
757 lines
23 KiB
C++
|
|
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2023 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 "tlFileUtils.h"
|
|
#include "tlGlobPattern.h"
|
|
#include "lymMacroCollection.h"
|
|
#include "rba.h"
|
|
#include "pya.h"
|
|
#include "gsiDecl.h"
|
|
#include "gsiExternalMain.h"
|
|
#include "dbStatic.h"
|
|
#include "dbInit.h"
|
|
|
|
// This hard-links the GSI test classes
|
|
#include "../gsi_test/gsiTestForceLink.h"
|
|
|
|
#include "version.h"
|
|
|
|
#if defined(HAVE_QT)
|
|
|
|
// For testing the document structure
|
|
# include "docForceLink.h"
|
|
# include "iconsForceLink.h"
|
|
|
|
# include "layApplication.h"
|
|
# include "layMainWindow.h"
|
|
# include "laySystemPaths.h"
|
|
# include "layVersion.h"
|
|
|
|
# include <QDir>
|
|
# include <QFileInfo>
|
|
# include <QTextCodec>
|
|
|
|
#endif
|
|
|
|
#if !defined(_WIN32)
|
|
# include <dlfcn.h>
|
|
#endif
|
|
#if defined(_WIN32)
|
|
# include <Windows.h>
|
|
#endif
|
|
|
|
// required to force linking of the "rdb", "lib" and "drc" module
|
|
// and the plugins/auxiliary modules (some in non-Qt case)
|
|
#include "libForceLink.h"
|
|
#include "rdbForceLink.h"
|
|
#include "antForceLink.h"
|
|
#include "imgForceLink.h"
|
|
#include "edtForceLink.h"
|
|
#include "lymForceLink.h"
|
|
#if defined(HAVE_RUBY)
|
|
# include "drcForceLink.h"
|
|
# include "lvsForceLink.h"
|
|
#endif
|
|
|
|
#if defined(HAVE_QTBINDINGS)
|
|
|
|
// pulls in the Qt GSI binding modules - need to be force loaded so they are available
|
|
// the pya Python module (Python >= 3.8 does not recognize DLL paths on Windows)
|
|
# include "gsiQtGuiExternals.h"
|
|
# include "gsiQtWidgetsExternals.h"
|
|
# include "gsiQtCoreExternals.h"
|
|
# include "gsiQtMultimediaExternals.h"
|
|
# include "gsiQtPrintSupportExternals.h"
|
|
# include "gsiQtXmlExternals.h"
|
|
# include "gsiQtXmlPatternsExternals.h"
|
|
# include "gsiQtSqlExternals.h"
|
|
# include "gsiQtSvgExternals.h"
|
|
# include "gsiQtNetworkExternals.h"
|
|
# include "gsiQtDesignerExternals.h"
|
|
# include "gsiQtUiToolsExternals.h"
|
|
|
|
FORCE_LINK_GSI_QTCORE
|
|
FORCE_LINK_GSI_QTGUI
|
|
FORCE_LINK_GSI_QTWIDGETS
|
|
FORCE_LINK_GSI_QTMULTIMEDIA
|
|
FORCE_LINK_GSI_QTPRINTSUPPORT
|
|
FORCE_LINK_GSI_QTXML
|
|
FORCE_LINK_GSI_QTXMLPATTERNS
|
|
FORCE_LINK_GSI_QTDESIGNER
|
|
FORCE_LINK_GSI_QTNETWORK
|
|
FORCE_LINK_GSI_QTSQL
|
|
FORCE_LINK_GSI_QTSVG
|
|
FORCE_LINK_GSI_QTUITOOLS
|
|
|
|
#else
|
|
# define QT_EXTERNAL_BASE(x)
|
|
#endif
|
|
|
|
static int main_cont (int &argc, char **argv);
|
|
|
|
#ifdef _WIN32 // for VC++
|
|
|
|
// for VC++/MinGW provide a wrapper for main.
|
|
#include <Windows.h>
|
|
|
|
extern "C"
|
|
int WINAPI
|
|
WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*prevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
|
|
{
|
|
int argCount = 0;
|
|
LPWSTR *szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount);
|
|
|
|
// fail safe behaviour
|
|
if (!szArgList) {
|
|
MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);
|
|
return 10;
|
|
}
|
|
|
|
char **argv = new char *[argCount];
|
|
for (int i = 0; i < argCount; i++) {
|
|
std::wstring a;
|
|
for (WCHAR *wc = szArgList [i]; *wc; ++wc) {
|
|
a += wchar_t ((unsigned int) *wc);
|
|
}
|
|
std::string aa = tl::to_string (a);
|
|
argv [i] = new char [aa.size () + 1];
|
|
strcpy (argv [i], aa.c_str ());
|
|
}
|
|
|
|
int ret = rba::RubyInterpreter::initialize (argCount, 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 ();
|
|
|
|
for (int i = 0; i < argCount; i++) {
|
|
delete[] argv [i];
|
|
}
|
|
delete[] argv;
|
|
|
|
LocalFree(szArgList);
|
|
return ret;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
main(int a_argc, const char **a_argv)
|
|
{
|
|
char **argv = new char *[a_argc];
|
|
for (int i = 0; i < a_argc; i++) {
|
|
tl::string aa = tl::system_to_string (a_argv[i]);
|
|
argv [i] = new char [aa.size () + 1];
|
|
strcpy (argv [i], aa.c_str ());
|
|
}
|
|
|
|
int ret = rba::RubyInterpreter::initialize (a_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 ();
|
|
|
|
for (int i = 0; i < a_argc; i++) {
|
|
delete[] argv [i];
|
|
}
|
|
delete[] argv;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool
|
|
run_test (tl::TestBase *t, bool editable, bool slow, int repeat)
|
|
{
|
|
#if defined(HAVE_QT)
|
|
// provide a clean main window without any views attached
|
|
if (lay::MainWindow::instance ()) {
|
|
lay::MainWindow::instance ()->close_all ();
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < repeat; ++i) {
|
|
if (repeat > 1) {
|
|
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, 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 ...";
|
|
|
|
db::set_default_editable_mode (e != 0);
|
|
#if defined(HAVE_QT)
|
|
lay::ApplicationBase::instance ()->set_editable (e != 0);
|
|
#endif
|
|
|
|
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::noctrl << "Memory: " << timer.memory_size () / 1024 << "k";
|
|
ut::ctrl << "<x-testcase-times wall=\"" << timer.sec_wall () << "\" user=\"" << timer.sec_user () << "\" sys=\"" << timer.sec_sys () << "\" memory=\"" << timer.memory_size () << "\"/>";
|
|
|
|
} 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)
|
|
{
|
|
std::unique_ptr<rba::RubyInterpreter> ruby_interpreter;
|
|
std::unique_ptr<pya::PythonInterpreter> python_interpreter;
|
|
|
|
#if defined(HAVE_QT)
|
|
|
|
// 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 ());
|
|
|
|
#endif
|
|
|
|
int result = 0;
|
|
|
|
ut::TestConsole console (stdout);
|
|
|
|
try {
|
|
|
|
gsi::initialize_external ();
|
|
|
|
// Search and initialize plugin unit tests
|
|
|
|
std::string inst_dir = tl::get_inst_path ();
|
|
std::vector<std::string> inst_modules = tl::dir_entries (inst_dir, true, false);
|
|
std::sort (inst_modules.begin (), inst_modules.end ());
|
|
|
|
for (std::vector<std::string>::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
|
|
|
|
if (tl::extension_last (*im) != "ut") {
|
|
continue;
|
|
}
|
|
|
|
std::string ut_file = tl::absolute_file_path (tl::combine_path (inst_dir, *im));
|
|
if (tl::file_exists (ut_file)) {
|
|
|
|
tl::log << "Loading unit tests " << ut_file;
|
|
|
|
// 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 (tl::to_wstring (ut_file).c_str ());
|
|
if (! handle) {
|
|
throw tl::Exception (tl::sprintf ("Unable to load plugin tests: %s with error message: %s", ut_file.c_str (), GetLastError ()));
|
|
}
|
|
#else
|
|
void *handle;
|
|
handle = dlopen (tl::string_to_system (ut_file).c_str (), RTLD_LAZY);
|
|
if (! handle) {
|
|
throw tl::Exception (tl::sprintf ("Unable to load plugin tests: %s", ut_file.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.");
|
|
}
|
|
|
|
#if defined(HAVE_QT)
|
|
|
|
// NOTE: we need an application object, but we don't call parse_cmd. This makes the object
|
|
// behave neutral as far as possible.
|
|
lay::GuiApplication app (argc, argv);
|
|
app.init_app ();
|
|
|
|
app.ruby_interpreter ().push_console (&console);
|
|
app.python_interpreter ().push_console (&console);
|
|
|
|
app.autorun ();
|
|
|
|
#if QT_VERSION < 0x050000
|
|
QTextCodec::setCodecForTr (QTextCodec::codecForName ("utf8"));
|
|
#endif
|
|
|
|
#else
|
|
|
|
// select the system locale
|
|
setlocale (LC_ALL, "");
|
|
|
|
// initialize the modules (load their plugins from the paths)
|
|
db::init ();
|
|
|
|
// initialize the GSI class system (Variant binding, Expression support)
|
|
// We have to do this now since plugins may register GSI classes and before the
|
|
// ruby interpreter, because it depends on a proper class system.
|
|
gsi::initialize ();
|
|
|
|
// initialize the tl::Expression subsystem with GSI-bound classes
|
|
gsi::initialize_expressions ();
|
|
|
|
// instantiate the interpreters
|
|
|
|
ruby_interpreter.reset (new rba::RubyInterpreter ());
|
|
ruby_interpreter->push_console (&console);
|
|
|
|
python_interpreter.reset (new pya::PythonInterpreter ());
|
|
python_interpreter->push_console (&console);
|
|
|
|
lym::MacroCollection &lym_root = lym::MacroCollection::root ();
|
|
lym_root.add_folder (tl::to_string (tr ("Built-In")), ":/built-in-macros", "macros", true);
|
|
lym_root.add_folder (tl::to_string (tr ("Built-In")), ":/built-in-pymacros", "pymacros", true);
|
|
|
|
lym_root.autorun_early ();
|
|
lym_root.autorun ();
|
|
|
|
#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;
|
|
std::string output;
|
|
|
|
tl::CommandLineOptions cmd;
|
|
cmd << tl::arg ("-a", &xml_format, "Provide XML output format (JUnit format)")
|
|
<< tl::arg ("-o=log", &output, "Sends output to the given file")
|
|
<< 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);
|
|
|
|
// set some global variables
|
|
if (rba::RubyInterpreter::instance ()) {
|
|
rba::RubyInterpreter::instance ()->define_variable ("ut_inst_path", tl::get_inst_path ());
|
|
}
|
|
if (pya::PythonInterpreter::instance ()) {
|
|
pya::PythonInterpreter::instance ()->define_variable ("ut_inst_path", tl::get_inst_path ());
|
|
}
|
|
|
|
FILE *output_file = 0;
|
|
|
|
try {
|
|
|
|
if (! output.empty ()) {
|
|
output_file = fopen (output.c_str (), "w");
|
|
if (! output_file) {
|
|
throw tl::Exception (std::string ("Unable to open log file for writing :") + output);
|
|
}
|
|
console.send_to (output_file);
|
|
}
|
|
|
|
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::absolute_file_path (tl::testtmp ());
|
|
|
|
std::vector<tl::TestBase *> subset;
|
|
|
|
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) {
|
|
tl::GlobPattern re (*m);
|
|
re.set_case_sensitive (false);
|
|
re.set_header_match (true);
|
|
if (re.match ((*i)->name ())) {
|
|
exclude = true;
|
|
}
|
|
}
|
|
|
|
if (test_list.empty ()) {
|
|
|
|
if (!exclude) {
|
|
subset.push_back (*i);
|
|
ut::noctrl << " " << (*i)->name ();
|
|
}
|
|
|
|
} else {
|
|
|
|
for (std::vector<std::string>::const_iterator m = test_list.begin (); !exclude && m != test_list.end (); ++m) {
|
|
tl::GlobPattern re (*m);
|
|
re.set_case_sensitive (false);
|
|
re.set_header_match (true);
|
|
if (re.match ((*i)->name ())) {
|
|
ut::noctrl << " " << (*i)->name ();
|
|
subset.push_back (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = run_tests (subset, editable, non_editable, slow, repeat, gsi_coverage, class_names);
|
|
|
|
ut::ctrl << "</testsuites>";
|
|
|
|
if (output_file) {
|
|
console.send_to (stdout);
|
|
fclose (output_file);
|
|
}
|
|
|
|
} catch (...) {
|
|
|
|
ut::ctrl << "</testsuites>";
|
|
|
|
if (output_file) {
|
|
console.send_to (stdout);
|
|
fclose (output_file);
|
|
}
|
|
|
|
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;
|
|
}
|