Added tests for include feature, some bug fixes and enhancements.

This commit is contained in:
Matthias Koefferlein 2020-08-30 22:48:59 +02:00
parent a69d65daa3
commit a01eb70891
14 changed files with 309 additions and 22 deletions

View File

@ -124,7 +124,7 @@ public:
std::pair<std::string, std::string> include_expansion (const lym::Macro *macro)
{
if (m_supports_include_expansion) {
return MacroInterpreter::include_expansion (macro);
return lym::MacroInterpreter::include_expansion (macro);
} else {
return std::pair<std::string, std::string> (macro->path (), macro->text ());
}
@ -573,27 +573,32 @@ Class<lym::Macro> decl_Macro ("lay", "Macro",
gsi::method ("real_path", &real_path,
"@brief Gets the real path for an include-encoded path and line number\n"
"\n"
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) or __file__ and __line__ (Python) will "
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) will "
"not have the proper values but encoded file names. This method allows retrieving the real file by using\n"
"\n"
"@code\n"
"# Ruby\n"
"real_file = RBA::Macro::real_path(__FILE__, __LINE__)\n"
"\n"
"# Python\n"
"real_file = pya::Macro::real_path(__file__, __line__)\n"
"@/code\n"
"\n"
"This substitution is not required for top-level macros as KLayout's interpreter will automatically use this "
"function instead of __FILE__ or __file__. Call this function when you need __FILE__ or __file__ from files "
"included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n"
"function instead of __FILE__. Call this function when you need __FILE__ from files "
"included through the languages mechanisms such as 'require' or 'load' where this substitution does not happen.\n"
"\n"
"For Python there is no equivalent for __LINE__, so you always have to use:\n"
"\n"
"@code\n"
"# Python"
"import inspect\n"
"real_file = pya.Macro.real_path(__file__, inspect.currentframe().f_back.f_lineno)\n"
"@/code\n"
"\n"
"This feature has been introduced in version 0.27."
) +
gsi::method ("real_line", &real_line,
"@brief Gets the real line number for an include-encoded path and line number\n"
"\n"
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) or __file__ and __line__ (Python) will "
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) will "
"not have the proper values but encoded file names. This method allows retrieving the real line number by using\n"
"\n"
"@code\n"
@ -605,8 +610,16 @@ Class<lym::Macro> decl_Macro ("lay", "Macro",
"@/code\n"
"\n"
"This substitution is not required for top-level macros as KLayout's interpreter will automatically use this "
"function instead of __LINE__ or __line__. Call this function when you need __LINE__ or __line__ from files "
"included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n"
"function instead of __FILE__. Call this function when you need __FILE__ from files "
"included through the languages mechanisms such as 'require' or 'load' where this substitution does not happen.\n"
"\n"
"For Python there is no equivalent for __LINE__, so you always have to use:\n"
"\n"
"@code\n"
"# Python"
"import inspect\n"
"real_line = pya.Macro.real_line(__file__, inspect.currentframe().f_back.f_lineno)\n"
"@/code\n"
"\n"
"This feature has been introduced in version 0.27."
),

View File

@ -71,11 +71,25 @@ MacroInterpreter::include_expansion (const lym::Macro *macro)
}
if (ip == Macro::Ruby) {
res.second = tl::replaced (res.second, "__FILE__", "RBA::Macro::real_path(__FILE__, __LINE__)");
res.second = tl::replaced (res.second, "__LINE__", "RBA::Macro::real_line(__FILE__, __LINE__)");
} else if (ip == Macro::Python) {
res.second = tl::replaced (res.second, "__file__", "pya.Macro.real_path(__file__, __line__)");
res.second = tl::replaced (res.second, "__line__", "pya.Macro.real_line(__file__, __line__)");
std::string subst;
const std::string file_const ("__FILE__");
const std::string line_const ("__LINE__");
for (const char *cp = res.second.c_str (); *cp; ) {
if (strncmp (cp, file_const.c_str (), file_const.size ()) == 0 && !isalnum (cp[file_const.size ()]) && cp[file_const.size ()] != '_') {
subst += "RBA::Macro::real_path(__FILE__, __LINE__)";
cp += file_const.size ();
} else if (strncmp (cp, line_const.c_str (), line_const.size ()) == 0 && !isalnum (cp[line_const.size ()]) && cp[line_const.size ()] != '_') {
subst += "RBA::Macro::real_line(__FILE__, __LINE__)";
cp += line_const.size ();
} else {
subst += *cp++;
}
}
res.second = subst;
}
}

View File

@ -23,9 +23,179 @@
#include "tlUnitTest.h"
TEST(1)
#include "lymMacro.h"
#include "lymMacroInterpreter.h"
#include "gsiInterpreter.h"
#include "rba.h"
#include "pya.h"
class TestCollectorConsole
: public gsi::Console
{
// TODO: add tests for lym specific things
throw tl::CancelException (); // skip this test to indicate that there is nothing yet
public:
TestCollectorConsole () { }
~TestCollectorConsole () { }
virtual void write_str (const char *text, output_stream)
{
m_text += text;
}
virtual void flush () { }
virtual bool is_tty () { return false; }
virtual int columns () { return 80; }
virtual int rows () { return 50; }
const std::string &text () const { return m_text; }
private:
std::string m_text;
};
#if defined(HAVE_RUBY)
TEST(1_BasicRuby)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.rb");
macro.set_interpreter (lym::Macro::Ruby);
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Hello, world!\n");
}
TEST(2_RubyInclude)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.rb");
macro.set_interpreter (lym::Macro::Ruby);
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Stop 1: m2.rb:2\nf: a_inc.rb:3\nStop 2: m2.rb:8\n");
}
TEST(11_DRCBasic)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.drc");
macro.set_interpreter (lym::Macro::DSLInterpreter);
macro.set_dsl_interpreter ("drc");
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Result: (500,500;500,2000;1000,2000;1000,500) in m1.drc:20\n");
}
TEST(12_DRCBasic)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.drc");
macro.set_interpreter (lym::Macro::DSLInterpreter);
macro.set_dsl_interpreter ("drc");
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Result: (500,500;500,2000;1000,2000;1000,500) in m2.drc:14\n");
}
#endif
#if defined(HAVE_PYTHON)
TEST(101_BasicPython)
{
tl_assert (pya::PythonInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.py");
macro.set_interpreter (lym::Macro::Python);
macro.load ();
TestCollectorConsole console;
pya::PythonInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
pya::PythonInterpreter::instance ()->remove_console (&console);
} catch (...) {
pya::PythonInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Hello, world!\n");
}
TEST(102_PythonInclude)
{
tl_assert (pya::PythonInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.py");
macro.set_interpreter (lym::Macro::Python);
macro.load ();
TestCollectorConsole console;
pya::PythonInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
pya::PythonInterpreter::instance ()->remove_console (&console);
} catch (...) {
pya::PythonInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Stop 1: m2.py:8\nf: a_inc.py:5\nStop 2: m2.py:14\n");
}
#endif

View File

@ -9,8 +9,8 @@ include($$PWD/../../lib_ut.pri)
SOURCES = \
lymBasicTests.cc
INCLUDEPATH += $$LYM_INC $$TL_INC $$GSI_INC
DEPENDPATH += $$LYM_INC $$TL_INC $$GSI_INC
INCLUDEPATH += $$RBA_INC $$PYA_INC $$LYM_INC $$TL_INC $$GSI_INC
DEPENDPATH += $$RBA_INC $$PYA_INC $$LYM_INC $$TL_INC $$GSI_INC
LIBS += -L$$DESTDIR_UT -lklayout_lym -lklayout_tl -lklayout_gsi
LIBS += -L$$DESTDIR_UT -lklayout_rba -lklayout_pya -lklayout_lym -lklayout_tl -lklayout_gsi

View File

@ -78,7 +78,13 @@ IncludeExpander::read (const std::string &path, tl::InputStream &is, std::string
tl::Extractor ex (l.c_str ());
if (ex.test ("#") && ex.test ("%include")) {
std::string include_path = tl::trim (ex.skip ());
std::string include_path;
if (*ex.skip () == '"' || *ex.skip () == '\'') {
ex.read_quoted (include_path);
ex.expect_end ();
} else {
include_path = tl::trim (ex.skip ());
}
// allow some customization by employing expression interpolation
include_path = tl::Eval ().interpolate (include_path);

9
testdata/lym/a_inc.drc vendored Normal file
View File

@ -0,0 +1,9 @@
# some prep steps
ly = RBA::Layout::new
ly.create_cell("TOP")
l1 = ly.layer(1, 0)
ly.top_cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
l2 = ly.layer(2, 0)
ly.top_cell.shapes(l2).insert(RBA::Box::new(500, 500, 1500, 2500))

6
testdata/lym/a_inc.py vendored Normal file
View File

@ -0,0 +1,6 @@
import os
def f():
return("f: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))

5
testdata/lym/a_inc.rb vendored Normal file
View File

@ -0,0 +1,5 @@
def f
return "f: " + File.basename(__FILE__) + ":" + __LINE__.to_s
end

22
testdata/lym/m1.drc vendored Normal file
View File

@ -0,0 +1,22 @@
# some prep steps
ly = RBA::Layout::new
ly.create_cell("TOP")
l1 = ly.layer(1, 0)
ly.top_cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
l2 = ly.layer(2, 0)
ly.top_cell.shapes(l2).insert(RBA::Box::new(500, 500, 1500, 2500))
# actual "DRC"
source(ly.top_cell)
l1_drc = input(1, 0)
l2_drc = input(2, 0)
(l1_drc & l2_drc).output(100, 0)
l100 = ly.layer(100, 0)
puts "Result: " + RBA::Region::new(ly.top_cell.begin_shapes_rec(l100)).to_s + " in " + File.basename(__FILE__) + ":" + __LINE__.to_s

2
testdata/lym/m1.py vendored Normal file
View File

@ -0,0 +1,2 @@
print("Hello, world!")

2
testdata/lym/m1.rb vendored Normal file
View File

@ -0,0 +1,2 @@
puts "Hello, world!"

14
testdata/lym/m2.drc vendored Normal file
View File

@ -0,0 +1,14 @@
# %include "a_inc.drc"
# actual "DRC"
source(ly.top_cell)
l1_drc = input(1, 0)
l2_drc = input(2, 0)
(l1_drc & l2_drc).output(100, 0)
l100 = ly.layer(100, 0)
puts "Result: " + RBA::Region::new(ly.top_cell.begin_shapes_rec(l100)).to_s + " in " + File.basename(__FILE__) + ":" + __LINE__.to_s

15
testdata/lym/m2.py vendored Normal file
View File

@ -0,0 +1,15 @@
import os
import inspect
def lineno():
return inspect.currentframe().f_back.f_lineno
print("Stop 1: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))
# %include a_inc.py
print(f())
print("Stop 2: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))

9
testdata/lym/m2.rb vendored Normal file
View File

@ -0,0 +1,9 @@
puts "Stop 1: " + File.basename(__FILE__) + ":" + __LINE__.to_s
# %include a_inc.rb
puts f
puts "Stop 2: " + File.basename(__FILE__) + ":" + __LINE__.to_s