diff --git a/src/drc/drc/built-in-macros/drc.lym b/src/drc/drc/built-in-macros/drc.lym index d914dfa81..3f103f38c 100644 --- a/src/drc/drc/built-in-macros/drc.lym +++ b/src/drc/drc/built-in-macros/drc.lym @@ -4181,14 +4181,14 @@ CODE @output_layout_file = nil else @output_layout = RBA::Layout::new - @output_cell = @output_layout.create_cell(cellname.to_s || "TOP") + @output_cell = cellname && @output_layout.create_cell(cellname.to_s) @output_layout_file = arg end elsif arg.is_a?(RBA::Layout) @output_layout = arg - @output_cell = @output_layout.create_cell(cellname.to_s || "TOP") + @output_cell = cellname && (@output_layout.cell(cellname.to_s) || @output_layout.create_cell(cellname.to_s)) @output_layout_file = nil elsif arg.is_a?(RBA::Cell) diff --git a/src/drc/unit_tests/drcBasicTests.cc b/src/drc/unit_tests/drcBasicTests.cc index fa8b37941..5ea3343e9 100644 --- a/src/drc/unit_tests/drcBasicTests.cc +++ b/src/drc/unit_tests/drcBasicTests.cc @@ -63,3 +63,37 @@ TEST(1) this->compare_layouts (layout, au, ut::NoNormalization); } +TEST(2) +{ + lym::Macro drc; + drc.set_text ( + "dbu 0.001\n" + "def compare(a, b, ex)\n" + " a = a.to_s\n" + " b = b.to_s\n" + " if a != b\n" + " raise(ex + \" (actual=#{a}, ref=#{b})\")\n" + " end\n" + "end\n" + "compare(0.1.um, 0.1, \"unexpected value when converting um\")\n" + "compare(0.1.micron, 0.1, \"unexpected value when converting micron\")\n" + "compare(0.1.um2, 0.1, \"unexpected value when converting um2\")\n" + "compare(0.1.mm2, 100000.0, \"unexpected value when converting mm2\")\n" + "compare(120.dbu, 0.12, \"unexpected value when converting dbu\")\n" + "compare((0.1.um + 120.dbu), 0.22, \"unexpected value when adding values\")\n" + "compare(0.1.mm, 100.0, \"unexpected value when converting mm\")\n" + "compare(1e-6.m, 1.0, \"unexpected value when converting m\")\n" + "compare(1.um, 1.0, \"unexpected value when converting integer um\")\n" + "compare(1.micron, 1.0, \"unexpected value when convering integer micron\")\n" + "compare(1.um2, 1.0, \"unexpected value when converting integer um2\")\n" + "compare(1.mm2, 1000000.0, \"unexpected value when converting integer mm2\")\n" + "compare((1.um + 120.dbu), 1.12, \"unexpected value when adding integer values\")\n" + "compare(1.mm, 1000.0, \"unexpected value when converting integer mm\")\n" + "compare(1.m, 1000000.0, \"unexpected value when converting integer m\")\n" + ); + drc.set_interpreter (lym::Macro::DSLInterpreter); + drc.set_dsl_interpreter ("drc-dsl"); + + EXPECT_EQ (drc.run (), 0); +} + diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc new file mode 100644 index 000000000..6beda7b00 --- /dev/null +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -0,0 +1,142 @@ + +/* + + 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 "dbReader.h" +#include "lymMacro.h" + +TEST(1) +{ + std::string rs = ut::testsrc (); + rs += "/testdata/drc/drcSimpleTests_1.drc"; + + std::string au = ut::testsrc (); + au += "/testdata/drc/drcSimpleTests_au1.gds"; + + std::string output = this->tmp_file ("tmp.gds"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_test_source = nil\n" + "$drc_test_target = \"%s\"\n" + , output) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + this->compare_layouts (layout, au, ut::NoNormalization); +} + +TEST(2) +{ + std::string rs = ut::testsrc (); + rs += "/testdata/drc/drcSimpleTests_2.drc"; + + std::string input = ut::testsrc (); + input += "/testdata/drc/drctest.gds"; + + std::string au = ut::testsrc (); + au += "/testdata/drc/drcSimpleTests_au2.gds"; + + std::string output = this->tmp_file ("tmp.gds"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_test_source = \"%s\"\n" + "$drc_test_target = \"%s\"\n" + , input, output) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + this->compare_layouts (layout, au, ut::NoNormalization); +} + +TEST(3) +{ + std::string rs = ut::testsrc (); + rs += "/testdata/drc/drcSimpleTests_3.drc"; + + std::string input = ut::testsrc (); + input += "/testdata/drc/drctest.gds"; + + std::string au = ut::testsrc (); + au += "/testdata/drc/drcSimpleTests_au3.gds"; + + std::string output = this->tmp_file ("tmp.gds"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_test_source = \"%s\"\n" + "$drc_test_target = \"%s\"\n" + , input, output) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + this->compare_layouts (layout, au, ut::NoNormalization); +} diff --git a/src/drc/unit_tests/drcSuiteTests.cc b/src/drc/unit_tests/drcSuiteTests.cc new file mode 100644 index 000000000..10c5a1ab4 --- /dev/null +++ b/src/drc/unit_tests/drcSuiteTests.cc @@ -0,0 +1,91 @@ + +/* + + 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 "dbReader.h" +#include "lymMacro.h" + +void runtest (ut::TestBase *_this, int mode) +{ + std::string rs = ut::testsrc (); + rs += "/testdata/drc/drcSuiteTests.drc"; + + std::string input = ut::testsrc (); + input += "/testdata/drc/drctest.gds"; + + std::string au = ut::testsrc (); + au += "/testdata/drc/drcSuiteTests_au"; + au += tl::to_string (mode); + au += ".gds"; + + std::string output = _this->tmp_file ("tmp.gds"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_test_source = \"%s\"\n" + "$drc_test_target = \"%s\"\n" + "$drc_test_mode = %d\n" + , input, output, mode) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + _this->compare_layouts (layout, au, ut::NoNormalization); +} + +TEST(1) +{ + runtest (_this, 1); +} + +TEST(2) +{ + test_is_long_runner (); + runtest (_this, 2); +} + +TEST(3) +{ + test_is_long_runner (); + runtest (_this, 3); +} + +TEST(4) +{ + test_is_long_runner (); + runtest (_this, 4); +} diff --git a/src/drc/unit_tests/unit_tests.pro b/src/drc/unit_tests/unit_tests.pro index a508e756f..1965c0fa1 100644 --- a/src/drc/unit_tests/unit_tests.pro +++ b/src/drc/unit_tests/unit_tests.pro @@ -9,6 +9,8 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ drcBasicTests.cc \ + drcSimpleTests.cc \ + drcSuiteTests.cc \ INCLUDEPATH += ../drc ../../rdb ../../db ../../tl ../../gsi ../../lym ../../ut DEPENDPATH += ../drc ../../rdb ../../db ../../tl ../../gsi ../../lym ../../ut diff --git a/src/unit_tests/dbOASISReader.cc b/src/unit_tests/dbOASISReader.cc index 9d9e8f247..23a4e7839 100644 --- a/src/unit_tests/dbOASISReader.cc +++ b/src/unit_tests/dbOASISReader.cc @@ -47,7 +47,7 @@ TEST(1_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -78,7 +78,7 @@ TEST(1_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -109,7 +109,7 @@ TEST(1_3) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -140,7 +140,7 @@ TEST(1_4) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -171,7 +171,7 @@ TEST(1_5) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -229,7 +229,7 @@ TEST(10_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -295,7 +295,7 @@ TEST(11_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -526,7 +526,7 @@ TEST(11_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -592,7 +592,7 @@ TEST(11_3) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -692,7 +692,7 @@ TEST(11_4) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -923,7 +923,7 @@ TEST(11_5) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1024,7 +1024,7 @@ TEST(11_6) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1067,7 +1067,7 @@ TEST(11_7) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1161,7 +1161,7 @@ TEST(12_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1211,7 +1211,7 @@ TEST(13_1) db::LayerMap map = reader.read (layout); EXPECT_EQ (map.to_string (), "layer_map('1/2 : \\'AA;L5A\\' (1/2)';'1/5 : \\'AA;L5A\\' (1/5)';'1/6 : \\'AA;L5A\\' (1/6)';'1/8 : \\'AA;L5A\\' (1/8)';'5/2 : \\'AA;L5A;H5A;E5A;I56A;E5L4\\' (5/2)';'5/5 : \\'AA;L5A;H5A;E5A;I56A;E5H4;E5I47\\' (5/5)';'5/6 : \\'AA;L5A;H5A;E5A;I56A;E5H4;E5I47\\' (5/6)';'5/8 : \\'AA;L5A;H5A;E5A;I56A;E5H4\\' (5/8)';'6/2 : \\'AA;H5A;I56A\\' (6/2)';'6/5 : \\'AA;H5A;I56A\\' (6/5)';'6/6 : \\'AA;H5A;I56A\\' (6/6)';'6/8 : \\'AA;H5A;I56A\\' (6/8)';'7/2 : \\'AA;H5A\\' (7/2)';'7/5 : \\'AA;H5A\\' (7/5)';'7/6 : \\'AA;H5A\\' (7/6)';'7/8 : \\'AA;H5A\\' (7/8)')") } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1261,7 +1261,7 @@ TEST(13_2) db::LayerMap map = reader.read (layout); EXPECT_EQ (map.to_string (), "layer_map('1/2 : \\'AA;L5A\\' (1/2)';'1/5 : \\'AA;L5A\\' (1/5)';'1/6 : \\'AA;L5A\\' (1/6)';'1/8 : \\'AA;L5A\\' (1/8)';'5/2 : \\'AA;L5A;H5A;E5A;I56A\\' (5/2)';'5/5 : \\'AA;L5A;H5A;E5A;I56A\\' (5/5)';'5/6 : \\'AA;L5A;H5A;E5A;I56A\\' (5/6)';'5/8 : \\'AA;L5A;H5A;E5A;I56A\\' (5/8)';'6/2 : \\'AA;H5A;I56A\\' (6/2)';'6/5 : \\'AA;H5A;I56A\\' (6/5)';'6/6 : \\'AA;H5A;I56A\\' (6/6)';'6/8 : \\'AA;H5A;I56A\\' (6/8)';'7/2 : \\'AA;H5A\\' (7/2)';'7/5 : \\'AA;H5A\\' (7/5)';'7/6 : \\'AA;H5A\\' (7/6)';'7/8 : \\'AA;H5A\\' (7/8)')"); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1327,7 +1327,7 @@ TEST(13_3) db::LayerMap map = reader.read (layout); EXPECT_EQ (map.to_string (), "layer_map('1/2 : \\'TAA;TL5A;AA;L5A\\' (1/2)';'1/5 : \\'TAA;TL5A;AA;L5A\\' (1/5)';'1/6 : \\'TAA;TL5A;AA;L5A\\' (1/6)';'1/8 : \\'TAA;TL5A;AA;L5A\\' (1/8)';'5/2 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/2)';'5/5 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/5)';'5/6 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/6)';'5/8 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/8)';'6/2 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/2)';'6/5 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/5)';'6/6 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/6)';'6/8 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/8)';'7/2 : \\'TAA;TH5A;AA;H5A\\' (7/2)';'7/5 : \\'TAA;TH5A;AA;H5A\\' (7/5)';'7/6 : \\'TAA;TH5A;AA;H5A\\' (7/6)';'7/8 : \\'TAA;TH5A;AA;H5A\\' (7/8)')"); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1393,7 +1393,7 @@ TEST(13_4) db::LayerMap map = reader.read (layout); EXPECT_EQ (map.to_string (), "layer_map('1/2 : \\'TAA;TL5A;AA;L5A\\' (1/2)';'1/5 : \\'TAA;TL5A;AA;L5A\\' (1/5)';'1/6 : \\'TAA;TL5A;AA;L5A\\' (1/6)';'1/8 : \\'TAA;TL5A;AA;L5A\\' (1/8)';'5/2 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/2)';'5/5 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/5)';'5/6 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/6)';'5/8 : \\'TAA;TL5A;TH5A;TE5A;TI56A;AA;L5A;H5A;E5A;I56A\\' (5/8)';'6/2 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/2)';'6/5 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/5)';'6/6 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/6)';'6/8 : \\'TAA;TH5A;TI56A;AA;H5A;I56A\\' (6/8)';'7/2 : \\'TAA;TH5A;AA;H5A\\' (7/2)';'7/5 : \\'TAA;TH5A;AA;H5A\\' (7/5)';'7/6 : \\'TAA;TH5A;AA;H5A\\' (7/6)';'7/8 : \\'TAA;TH5A;AA;H5A\\' (7/8)')"); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1436,7 +1436,7 @@ TEST(14_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1469,7 +1469,7 @@ TEST(2_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1504,7 +1504,7 @@ TEST(2_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1561,7 +1561,7 @@ TEST(2_4) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1618,7 +1618,7 @@ TEST(2_6) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1869,7 +1869,7 @@ TEST(3_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -1903,7 +1903,7 @@ TEST(3_10) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -2176,7 +2176,7 @@ TEST(3_12) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -2427,7 +2427,7 @@ TEST(3_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -2722,7 +2722,7 @@ TEST(3_5) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -2822,7 +2822,7 @@ TEST(3_9) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -2879,7 +2879,7 @@ TEST(4_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3008,7 +3008,7 @@ TEST(4_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3077,7 +3077,7 @@ TEST(5_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3113,7 +3113,7 @@ TEST(5_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3291,7 +3291,7 @@ TEST(5_3) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3354,7 +3354,7 @@ TEST(6_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3488,7 +3488,7 @@ TEST(7_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3540,7 +3540,7 @@ TEST(8_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3583,7 +3583,7 @@ TEST(8_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3626,7 +3626,7 @@ TEST(8_3) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3669,7 +3669,7 @@ TEST(8_4) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3712,7 +3712,7 @@ TEST(8_5) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3759,7 +3759,7 @@ TEST(8_6) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3802,7 +3802,7 @@ TEST(8_7) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3845,7 +3845,7 @@ TEST(8_8) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3945,7 +3945,7 @@ TEST(9_1) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -3984,7 +3984,7 @@ TEST(9_2) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -4036,7 +4036,7 @@ TEST(99) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) @@ -4053,7 +4053,7 @@ TEST(99) try { db::LayerMap map = reader.read (layout); } catch (tl::Exception &ex) { - ut::print_error (ex.msg ()); + tl::error << ex.msg (); error = true; } EXPECT_EQ (error, false) diff --git a/src/unit_tests/tlEvents.cc b/src/unit_tests/tlEvents.cc index a91ae0e8a..29bbba07d 100644 --- a/src/unit_tests/tlEvents.cc +++ b/src/unit_tests/tlEvents.cc @@ -24,6 +24,7 @@ #include "tlEvents.h" #include "utHead.h" +#include // Object with event class Observed diff --git a/src/ut/ut.pro b/src/ut/ut.pro index 4633385ca..2ca3b436d 100644 --- a/src/ut/ut.pro +++ b/src/ut/ut.pro @@ -11,10 +11,14 @@ TEMPLATE = lib # Input HEADERS = \ utHead.h \ - utCommon.h + utTestBase.h \ + utTestConsole.h \ + utCommon.h SOURCES = \ utMain.cc \ + utTestConsole.cc \ + utTestBase.cc \ INCLUDEPATH = ../tl ../db ../gsi ../lay ../ext ../lib DEPENDPATH = ../tl ../db ../gsi ../lay ../ext ../lib diff --git a/src/ut/utHead.h b/src/ut/utHead.h index a9c06c198..5177bc3e4 100644 --- a/src/ut/utHead.h +++ b/src/ut/utHead.h @@ -24,482 +24,20 @@ #ifndef HDR_utHead #define HDR_utHead -#include -#include -#include -#include -#include -#include - #include "utCommon.h" -#include "tlString.h" -#include "tlException.h" -#include "tlStaticObjects.h" -#include "dbStatic.h" -#include "dbTypes.h" -#include "gsiExpression.h" -#include "gsiInterpreter.h" - -#include -#include - -namespace db { - class Layout; - class LayerMap; -} - -namespace rba { - class RubyInterpreter; -} - -namespace pya { - class PythonInterpreter; -} +#include "utTestBase.h" +#include "utTestConsole.h" namespace ut { +extern tl::LogTee ctrl; +extern tl::LogTee noctrl; + /** * @brief The unit test execution function */ UT_PUBLIC int main (int argc, char **argv); -/** - * @brief Prints a error message to the unit test output stream - */ -UT_PUBLIC void print_error (const std::string &s); - -/** - * @brief A detailed diff printer - */ -UT_PUBLIC void write_detailed_diff (std::ostream &os, const std::string &subject, const std::string &ref); - -/** - * @brief Returns true, if the test is run in verbose mode - * Verbose mode is enabled through the "-v" option - */ -UT_PUBLIC bool verbose (); - -/** - * @brief Returns the Ruby interpreter - */ -UT_PUBLIC rba::RubyInterpreter *ruby_interpreter (); - -/** - * @brief Returns the Python interpreter - */ -UT_PUBLIC pya::PythonInterpreter *python_interpreter (); - -/** - * @brief Returns true, if the unit test is run in debug mode - * In debug mode, the unit tests shall offer information on how to fix the - * test. Specifically if layout compare is involved, it shall display the golden - * file name and the actual one and terminate to allow updating the files. - */ -UT_PUBLIC bool is_debug_mode (); - -/** - * @brief Specifies the normalization mode for compare_layouts - */ -enum NormalizationMode -{ - NoNormalization, // no normalization - take the test subject as it is - WriteGDS2, // normalize subject by writing to GDS2 and reading back - WriteOAS // normalize subject by writing to OASIS and reading back -}; - -/** - * @brief Gets the path of the test data - * This path is specified through the environment variable $TESTSRC - */ -UT_PUBLIC std::string testsrc (); - -/** - * @brief Gets the path of the private test data - * This path is specified through the environment variable $TESTSRC and the - * private testdata directory. If no private test data is available, this - * method will throw a CancelException which makes the test skipped. - */ -UT_PUBLIC std::string testsrc_private (); - -/** - * @brief A basic exception for the unit test framework - */ -struct Exception - : public tl::Exception -{ - Exception (const std::string &msg) - : tl::Exception (msg) - { } -}; - -/** - * @brief A utility class to capture the warning, error and info channels - * - * Instantiate this class inside a test. Then run the test and finally - * obtain the collected output with CaptureChannel::captured_text(). - */ -class UT_PUBLIC CaptureChannel : public tl::Channel -{ -public: - CaptureChannel (); - - std::string captured_text () const - { - return m_text.str (); - } - - void clear () - { - m_text.str (std::string ()); - } - -protected: - virtual void puts (const char *s); - virtual void endl (); - virtual void end (); - virtual void begin (); - -private: - std::ostringstream m_text; -}; - -/** - * @brief A generic compare operator - */ -template -inline bool equals (const X &a, const Y &b) -{ - return a == b; -} - -/** - * @brief A specialization of the compare operator for doubles - */ -UT_PUBLIC bool equals (double a, double b); - -/** - * @brief Specialization of comparison of pointers vs. integers (specifically "0") - */ -template -inline bool equals (X *a, int b) -{ - return a == (X *) size_t (b); -} - -/** - * @brief A specialization of comparison of double vs "anything" - */ -template -inline bool equals (double a, const Y &b) -{ - return equals (a, double (b)); -} - -/** - * @brief A specialization of comparison of "anything" vs. double - */ -template -inline bool equals (const X &a, double b) -{ - return equals (double (a), b); -} - -/** - * @brief A specialization of the compare operator for const char * - */ -inline bool equals (const char *a, const char *b) -{ - return equals (std::string (a), std::string (b)); -} - -/** - * @brief A specialization of the compare operator for std::string vs. const char * - */ -inline bool equals (const std::string &a, const char *b) -{ - return equals (a, std::string (b)); -} - -/** - * @brief A specialization of the compare operator for std::string vs. const char * - */ -inline bool equals (const char *a, const std::string &b) -{ - return equals (std::string (a), b); -} - -/** - * @brief The base class for tests - */ -struct UT_PUBLIC TestBase -{ - /** - * @brief Constructor - */ - TestBase (const std::string &file, const std::string &name); - - /** - * @brief Destructor - */ - virtual ~TestBase () { } - - /** - * @brief Actually runs the test - * @return True, if the test was successful - */ - bool do_test (const std::string &mode); - - /** - * @brief Raises an exception with the given string - * This version prints the last checkpoint for reference. - */ - void raise (const std::string &s); - - /** - * @brief Raises an exception with the given string, file and line number - */ - void raise (const std::string &file, int line, const std::string &s); - - /** - * @brief Registers a checkpoint - */ - void checkpoint (const std::string &file, int line); - - /** - * @brief Resets the checkpoints set - */ - void reset_checkpoint (); - - /** - * @brief Compares a layout with a golden layout file - * @param layout The layout to compare - * @param au_file The golden file path - * @param norm The normalization mode (see NormalizationMode) - * @param tolerance A tolerance applied when comparing shapes in database units - * The layout is normalized by writing to a file and reading back - */ - void compare_layouts (const db::Layout &layout, const std::string &au_file, NormalizationMode norm = WriteGDS2, db::Coord tolerance = 0); - - /** - * @brief Compares a layout with a golden layout file with layer mapping - * @param layout The layout to compare - * @param au_file The golden file path - * @param lmap The layer mapping object - * @param read_all_others If true, all other layers will be read too - * @param norm The normalization mode (see NormalizationMode) - * @param tolerance A tolerance applied when comparing shapes in database units - * The layout is normalized by writing to a file and reading back - */ - void compare_layouts (const db::Layout &layout, const std::string &au_file, const db::LayerMap &lmap, bool read_all_others, NormalizationMode norm = WriteGDS2, db::Coord tolerance = 0); - - /** - * @brief Compares two text files - */ - void compare_text_files (const std::string &path_a, const std::string &path_b); - - /** - * @brief The test's name - * @return The name of the test - */ - const std::string &name () const - { - return m_test; - } - - /** - * @brief Prepares a temporary file path - * @param fn The actual name of the file - * @return A path suitable for writing a temporary file - * The directory for the file will be created within this method. - */ - std::string tmp_file (const std::string &fn = "tmp") const; - - /** - * @brief Removes all temporary files - */ - void remove_tmp_folder (); - - /** - * @brief A generic diff printer - */ - template - void diff (const std::string &file, int line, const std::string &msg, const X &subject, const Y & /*ref*/) - { - std::ostringstream sstr; - sstr << msg << " (actual value is " << subject << ")"; - raise (file, line, sstr.str ()); - } - - /** - * @brief A generic diff printer - */ - template - void detailed_diff (const std::string &file, int line, const std::string &msg, const X &subject, const Y &ref) - { - std::ostringstream sstr; - sstr << msg << std::endl; - ut::write_detailed_diff (sstr, tl::to_string (subject), tl::to_string (ref)); - raise (file, line, sstr.str ()); - } - - /** - * @brief A diff printer for int vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, int subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for unsigned int vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, unsigned int subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for long vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, long subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for unsigned long vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, unsigned long subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for long long vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, long long subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for unsigned long long vs. something - */ - template - void diff (const std::string &file, int line, const std::string &msg, unsigned long long subject, const Y &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for bool - */ - inline void diff (const std::string &file, int line, const std::string &msg, bool subject, bool ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for double - */ - inline void diff (const std::string &file, int line, const std::string &msg, double subject, double ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for strings - */ - inline void diff (const std::string &file, int line, const std::string &msg, const std::string &subject, const std::string &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for strings vs. const char * - */ - inline void diff (const std::string &file, int line, const std::string &msg, const std::string &subject, const char *ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for strings vs. const char * - */ - inline void diff (const std::string &file, int line, const std::string &msg, const char *subject, const std::string &ref) - { - detailed_diff (file, line, msg, subject, ref); - } - - /** - * @brief A diff printer for C strings - */ - inline void diff (const std::string &file, int line, const std::string &msg, const char *subject, const char *ref) - { - diff (file, line, msg, std::string (subject), std::string (ref)); - } - - /** - * @brief Main entry point for the compare feature (EXPECT_EQ and EXPECT_NE) - */ - template - void eq_helper (bool eq, const T1 &a, const T2 &b, const char *what_expr, const char *equals_expr, const char *file, int line) - { - if (ut::equals (a, b) != eq) { - std::ostringstream sstr; - sstr << what_expr << " does not equal " << equals_expr; - diff (file, line, sstr.str (), a, b); - } - } - -private: - virtual void execute (ut::TestBase *_this) throw (tl::Exception) = 0; - - std::string m_test; - std::string m_testdir; - // last checkpoint - std::string m_cp_file; - int m_cp_line; - bool m_any_failed; - QString m_testtmp; -}; - -/** - * @brief The registration facility for tests - */ -struct Registrar -{ - static void reg (ut::TestBase *t) - { - if (! ms_instance) { - ms_instance = new Registrar (); - } - ms_instance->m_tests.push_back (t); - } - - static Registrar *instance () - { - return ms_instance; - } - - const std::vector &tests () const - { - return m_tests; - } - -private: - static Registrar *ms_instance; - - Registrar () : m_tests () { } - - std::vector m_tests; -}; - } // namespace ut #define TEST(NAME) \ diff --git a/src/ut/utMain.cc b/src/ut/utMain.cc index aa8e09718..ba6aa4fa6 100644 --- a/src/ut/utMain.cc +++ b/src/ut/utMain.cc @@ -20,110 +20,33 @@ */ - #include "utHead.h" -#include "rba.h" -#include "pya.h" #include "tlStaticObjects.h" #include "tlTimer.h" #include "tlSystemPaths.h" -#include "tlFileUtils.h" +#include "tlCommandLineParser.h" #include "layApplication.h" -#include "gsiExpression.h" -#include "gsiExternalMain.h" +#include "rba.h" +#include "pya.h" #include "gsiDecl.h" -#include "dbStatic.h" +#include "gsiExternalMain.h" -#include "dbLayoutDiff.h" -#include "dbWriter.h" -#include "dbGDS2Reader.h" -#include "dbOASISReader.h" -#include "dbGDS2Writer.h" -#include "dbOASISWriter.h" -#include "dbGDS2Reader.h" - -#include -#include +#include +#include +#include #if !defined(_WIN32) -# include # 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) +// TODO: move this to tlString.h +static std::string replicate (const char *s, size_t n) { std::string res; res.reserve (strlen (s) * n); @@ -134,394 +57,8 @@ std::string replicate (const char *s, size_t n) return res; } -void print_error (const std::string &s) -{ - tl::error << "ERROR: " << s; -} - -// ------------------------------------------------ -// CaptureChannel implementation - -CaptureChannel::CaptureChannel () -{ - tl::info.add (this, false); - tl::error.add (this, false); - tl::warn.add (this, false); -} - -void CaptureChannel::puts (const char *s) -{ - m_text << s; -} - -void CaptureChannel::endl () -{ - m_text << "\n"; -} - -void CaptureChannel::end () -{ - // .. nothing yet .. -} - -void CaptureChannel::begin () -{ - // .. nothing yet .. -} - -// ------------------------------------------------ -// 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; -}; - -std::string +// TODO: move this to tlString.h +static std::string escape_xml (const std::string &s) { std::string res; @@ -541,302 +78,6 @@ escape_xml (const std::string &s) return res; } -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*/) -{ - ut::ctrl << ""; - - try { - - // 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::ctrl << ""; - - ut::noctrl << "Time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)"; - ut::ctrl << ""; - - } catch (...) { - ut::ctrl << ""; - throw; - } - - 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\n actual: %s\n golden: %s%s", - tl::to_string (QFileInfo (tl::to_qstring (tmp_file)).absoluteFilePath ()), - tl::to_string (QFileInfo (tl::to_qstring (au_file)).absoluteFilePath ()), - (n > 1 ? "\nand variants" : ""))); - } -} - -static std::string read_file (const std::string &path) -{ - QFile file (tl::to_qstring (path)); - if (! file.open (QIODevice::ReadOnly | QIODevice::Text)) { - tl::warn << tl::sprintf ("Unable to open file %s", path); - } - - QByteArray ba = file.readAll (); - return std::string (ba.constData (), 0, ba.size ()); -} - -void TestBase::compare_text_files (const std::string &path_a, const std::string &path_b) -{ - std::string text_a = read_file (path_a); - std::string text_b = read_file (path_b); - - if (text_a != text_b) { - raise (tl::sprintf ("Compare failed - see:\n file 1: %s\n file 2: %s", - tl::to_string (QFileInfo (tl::to_qstring (path_a)).absoluteFilePath ()), - tl::to_string (QFileInfo (tl::to_qstring (path_b)).absoluteFilePath ()))); - } -} - - static int main_cont (int argc, char **argv); int @@ -851,197 +92,371 @@ main (int argc, char **argv) return ret; } +int +run_tests (const std::vector &selected_tests, bool editable, bool non_editable, bool slow, lay::Application &app, bool gsi_coverage, const std::vector &class_names_vector) +{ + std::set 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 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::ctrl << ""; + + ut::noctrl << replicate ("=", TestConsole::instance ()->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 () << "\">"; + + ut::noctrl << replicate ("-", TestConsole::instance ()->real_columns ()); + ut::noctrl << "Running " << (*t)->name (); + + try { + + if (! (*t)->do_test (e != 0, slow)) { + + ut::ctrl << "name ()) << " failed (continued mode - see previous messages)" << "\"/>"; + tl::error << "Test " << (*t)->name () << " failed (continued mode - see previous messages)"; + + failed_tests.push_back (*t); + ++failed; + + } + + } 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 (); + + 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 ("=", 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 << ""; + + ut::noctrl << "Total time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)"; + ut::ctrl << ""; + + ut::ctrl << ""; + + } + + } + + grand_timer.stop (); + + // GSI diagnostics: print all methods that have not been called + if (gsi_coverage) { + + ut::noctrl << replicate ("=", TestConsole::instance ()->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 && !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 (*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 (" ", TestConsole::instance ()->indent ()) << "Class " << c->name (); + first_of_class = false; + } + tl::warn << replicate (" ", TestConsole::instance ()->indent () * 2) << (*m)->to_string (); + + } + + } + + } + + if (first) { + tl::info << "GSI coverage test passed."; + } + + ut::ctrl << ""; + + } + + ut::noctrl << ut::replicate ("=", TestConsole::instance ()->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 (" ", TestConsole::instance ()->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 (" ", TestConsole::instance ()->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 ::const_iterator f = failed_tests_ne.begin (); f != failed_tests_ne.end (); ++f) { + tl::warn << replicate (" ", TestConsole::instance ()->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 (" ", TestConsole::instance ()->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 << ""; + + return result; +} + int main_cont (int argc, char **argv) { - 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 (tl::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) { - std::cerr << tl::sprintf ("Unable to load plugin tests: %s with error message: %s", pp.c_str (), GetLastError ()) << std::endl; - exit (1); - } -#else - void *handle; - handle = dlopen (tl::string_to_system (pp).c_str (), RTLD_LAZY); - if (! handle) { - std::cerr << tl::sprintf ("Unable to load plugin tests: %s", pp.c_str ()) << std::endl; - exit (1); - } -#endif - - } - - } - - // No side effects - tl::set_klayout_path (std::vector ()); - - 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); - app.autorun (); - -#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; + pya::PythonInterpreter::initialize (); + gsi::initialize_external (); - grand_timer.start (); + // Search and initialize plugin unit tests - ut::sp_ruby_interpreter = dynamic_cast (&app.ruby_interpreter ()); - ut::sp_python_interpreter = dynamic_cast (&app.python_interpreter ()); + QStringList name_filters; + name_filters << QString::fromUtf8 ("*.ut"); + + QDir inst_dir (tl::to_qstring (tl::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 (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::ctrl << ""; + // No side effects + tl::set_klayout_path (std::vector ()); + + 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); + app.autorun (); + + #if QT_VERSION < 0x050000 + QTextCodec::setCodecForTr (QTextCodec::codecForName ("utf8")); + #endif + + bool editable = false, non_editable = false; + bool gsi_coverage = false; + std::vector class_names; + std::vector test_list; + std::vector 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; + + 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 ("*-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 present multiple times." + ) + << 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::const_iterator i = ut::Registrar::instance()->tests ().begin (); i != ut::Registrar::instance()->tests ().end (); ++i) { + tl::info << " " << (*i)->name (); + } + throw tl::CancelException (); + } + + ut::set_verbose (verbose); + ut::set_continue_flag (continue_flag); + ut::set_debug_mode (debug_mode); + + ut::TestConsole console (stdout, xml_format); 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 ()); + tl::info << "TESTTMP=" << tl::to_string (QDir (tl::to_qstring (ut::testtmp ())).absolutePath ()); const std::vector *selected_tests = 0; std::vector subset; @@ -1053,11 +468,11 @@ main_cont (int argc, char **argv) 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) { + + for (std::vector::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; - break; } } @@ -1076,236 +491,22 @@ main_cont (int argc, char **argv) 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; + try { - for (int e = 0; e < 2; ++e) { + ut::ctrl << ""; + ut::ctrl << ""; - if ((non_editable && e == 0) || (editable && e == 1)) { + result = run_tests (*selected_tests, editable, non_editable, slow, app, gsi_coverage, class_names); - std::string mode (e == 0 ? "non-editable" : "editable"); - ut::ctrl << ""; - - 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 () << "\">"; - - ut::noctrl << replicate ("-", TestConsole::instance ()->real_columns ()); - ut::noctrl << "Running " << (*t)->name (); - - try { - - if (! (*t)->do_test (mode)) { - - ut::ctrl << "name ()) << " failed (continued mode - see previous messages)" << "\"/>"; - tl::error << "Test " << (*t)->name () << " failed (continued mode - see previous messages)"; - - failed_tests.push_back (*t); - ++failed; - - } - - } 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 (); - - 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 << ""; - - ut::ctrl << ""; - - } + ut::ctrl << ""; + } catch (...) { + ut::ctrl << ""; + throw; } - 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 (skipped_e + skipped_ne) << " 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::CancelException &) { + result = 0; } catch (tl::Exception &ex) { tl::error << ex.msg (); result = -1; @@ -1317,12 +518,8 @@ main_cont (int argc, char **argv) result = -1; } - ut::ctrl << ""; - return result; } - - } // namespace ut diff --git a/src/ut/utTestBase.cc b/src/ut/utTestBase.cc new file mode 100644 index 000000000..2b9fedf59 --- /dev/null +++ b/src/ut/utTestBase.cc @@ -0,0 +1,458 @@ + +/* + + 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 "utTestBase.h" +#include "utTestConsole.h" +#include "utHead.h" +#include "tlFileUtils.h" +#include "tlTimer.h" +#include "tlStream.h" +#include "dbLayout.h" +#include "dbStreamLayers.h" +#include "dbGDS2Writer.h" +#include "dbOASISWriter.h" +#include "dbReader.h" +#include "dbCommonReader.h" +#include "dbLayoutDiff.h" + +#include "pya.h" +#include "rba.h" + +#include + +namespace ut +{ + +// -------------------------------------------------------------------------------------- + +static bool s_verbose_flag = false; +static bool s_debug_mode = false; +static bool s_continue_flag = false; + +bool verbose () +{ + return s_verbose_flag; +} + +void set_verbose (bool f) +{ + s_verbose_flag = f; +} + +void set_continue_flag (bool f) +{ + s_continue_flag = f; +} + +bool is_debug_mode () +{ + return s_debug_mode; +} + +void set_debug_mode (bool f) +{ + s_debug_mode = f; +} + +pya::PythonInterpreter *python_interpreter () +{ + pya::PythonInterpreter *ip = pya::PythonInterpreter::instance (); + tl_assert (ip != 0); + return ip; +} + +rba::RubyInterpreter *ruby_interpreter () +{ + rba::RubyInterpreter *ip = rba::RubyInterpreter::instance (); + tl_assert (ip != 0); + return ip; +} + +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 ()); +} + +std::string testtmp () +{ + // Ensures the test temp directory is present + const char *tt = getenv ("TESTTMP"); + if (! tt) { + throw tl::Exception ("TESTTMP undefined"); + } + return tt; +} + +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; + } +} + +// TODO: move this to tlString.h +static 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; +} + +// -------------------------------------------------------------------------------------- +// TestBase implementation + +ut::Registrar *ut::Registrar::ms_instance = 0; + +TestBase::TestBase (const std::string &file, const std::string &name) + : m_editable (false), m_slow (false), 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 (bool editable, bool slow) +{ + m_editable = editable; + m_slow = slow; + + ut::ctrl << ""; + + try { + + // Ensures the test temp directory is present + QDir dir (tl::to_qstring (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::ctrl << ""; + + ut::noctrl << "Time: " << timer.sec_wall () << "s (wall) " << timer.sec_user () << "s (user) " << timer.sec_sys () << "s (sys)"; + ut::ctrl << ""; + + } catch (...) { + ut::ctrl << ""; + throw; + } + + 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 (tl::to_qstring (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::test_is_editable_only () +{ + if (!m_editable) { + throw tl::CancelException (); + } +} + +void TestBase::test_is_non_editable_only () +{ + if (m_editable) { + throw tl::CancelException (); + } +} + +void TestBase::test_is_long_runner () +{ + if (!m_slow) { + throw tl::CancelException (); + } +} + +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\n actual: %s\n golden: %s%s", + tl::to_string (QFileInfo (tl::to_qstring (tmp_file)).absoluteFilePath ()), + tl::to_string (QFileInfo (tl::to_qstring (au_file)).absoluteFilePath ()), + (n > 1 ? "\nand variants" : ""))); + } +} + +void TestBase::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 + ; +} + +static std::string read_file (const std::string &path) +{ + QFile file (tl::to_qstring (path)); + if (! file.open (QIODevice::ReadOnly | QIODevice::Text)) { + tl::warn << tl::sprintf ("Unable to open file %s", path); + } + + QByteArray ba = file.readAll (); + return std::string (ba.constData (), 0, ba.size ()); +} + +void TestBase::compare_text_files (const std::string &path_a, const std::string &path_b) +{ + std::string text_a = read_file (path_a); + std::string text_b = read_file (path_b); + + if (text_a != text_b) { + raise (tl::sprintf ("Compare failed - see:\n file 1: %s\n file 2: %s", + tl::to_string (QFileInfo (tl::to_qstring (path_a)).absoluteFilePath ()), + tl::to_string (QFileInfo (tl::to_qstring (path_b)).absoluteFilePath ()))); + } +} + +} diff --git a/src/ut/utTestBase.h b/src/ut/utTestBase.h new file mode 100644 index 000000000..796475661 --- /dev/null +++ b/src/ut/utTestBase.h @@ -0,0 +1,498 @@ + +/* + + 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 + +*/ + + +#ifndef HDR_utTestBase +#define HDR_utTestBase + +#include "utCommon.h" +#include "dbTypes.h" +#include "tlException.h" +#include "tlString.h" + +#include +#include +#include +#include + +namespace db +{ + class Layout; + class LayerMap; +} + +namespace rba +{ + class RubyInterpreter; +} + +namespace pya +{ + class PythonInterpreter; +} + +namespace ut +{ + +/** + * @brief Returns true, if the test is run in verbose mode + * Verbose mode is enabled through the "-v" option + */ +UT_PUBLIC bool verbose (); + +/** + * @brief Sets verbose mode + */ +UT_PUBLIC void set_verbose (bool v); + +/** + * @brief Enables or disables "continue" mode + * In continue mode, the execution will proceed even in case of an error. + */ +UT_PUBLIC void set_continue_flag (bool f); + +/** + * @brief Returns the Ruby interpreter + */ +UT_PUBLIC rba::RubyInterpreter *ruby_interpreter (); + +/** + * @brief Returns the Python interpreter + */ +UT_PUBLIC pya::PythonInterpreter *python_interpreter (); + +/** + * @brief Returns true, if the unit test is run in debug mode + * In debug mode, the unit tests shall offer information on how to fix the + * test. Specifically if layout compare is involved, it shall display the golden + * file name and the actual one and terminate to allow updating the files. + */ +UT_PUBLIC bool is_debug_mode (); + +/** + * @brief Enables or disables debug mode + */ +UT_PUBLIC void set_debug_mode (bool f); + +/** + * @brief Gets the path of the test data + * This path is specified through the environment variable $TESTSRC + */ +UT_PUBLIC std::string testsrc (); + +/** + * @brief Gets the path of the private test data + * This path is specified through the environment variable $TESTSRC and the + * private testdata directory. If no private test data is available, this + * method will throw a CancelException which makes the test skipped. + */ +UT_PUBLIC std::string testsrc_private (); + +/** + * @brief Gets the path to the temporary data + * This path is specified through the environment variable $TESTTMP + */ +UT_PUBLIC std::string testtmp (); + +/** + * @brief A basic exception for the unit test framework + */ +struct Exception + : public tl::Exception +{ + Exception (const std::string &msg) + : tl::Exception (msg) + { } +}; + +/** + * @brief Specifies the normalization mode for compare_layouts + */ +enum NormalizationMode +{ + NoNormalization, // no normalization - take the test subject as it is + WriteGDS2, // normalize subject by writing to GDS2 and reading back + WriteOAS // normalize subject by writing to OASIS and reading back +}; + +/** + * @brief A generic compare operator + */ +template +inline bool equals (const X &a, const Y &b) +{ + return a == b; +} + +/** + * @brief A specialization of the compare operator for doubles + */ +UT_PUBLIC bool equals (double a, double b); + +/** + * @brief Specialization of comparison of pointers vs. integers (specifically "0") + */ +template +inline bool equals (X *a, int b) +{ + return a == (X *) size_t (b); +} + +/** + * @brief A specialization of comparison of double vs "anything" + */ +template +inline bool equals (double a, const Y &b) +{ + return equals (a, double (b)); +} + +/** + * @brief A specialization of comparison of "anything" vs. double + */ +template +inline bool equals (const X &a, double b) +{ + return equals (double (a), b); +} + +/** + * @brief A specialization of the compare operator for const char * + */ +inline bool equals (const char *a, const char *b) +{ + return equals (std::string (a), std::string (b)); +} + +/** + * @brief A specialization of the compare operator for std::string vs. const char * + */ +inline bool equals (const std::string &a, const char *b) +{ + return equals (a, std::string (b)); +} + +/** + * @brief A specialization of the compare operator for std::string vs. const char * + */ +inline bool equals (const char *a, const std::string &b) +{ + return equals (std::string (a), b); +} + +/** + * @brief The base class for tests + */ +struct UT_PUBLIC TestBase +{ + /** + * @brief Constructor + */ + TestBase (const std::string &file, const std::string &name); + + /** + * @brief Destructor + */ + virtual ~TestBase () { } + + /** + * @brief Actually runs the test + * @return True, if the test was successful + */ + bool do_test (bool editable, bool slow); + + /** + * @brief Indicates that the test is a long runner + * In this case the test is skipped unless in slow mode. + */ + void test_is_long_runner (); + + /** + * @brief Indicates that the test is a test that should run in editable mode only. + * In this case the test is skipped unless in editable mode. + */ + void test_is_editable_only (); + + /** + * @brief Indicates that the test is a test that should run in non-editable mode only. + * In this case the test is skipped unless in non-editable mode. + */ + void test_is_non_editable_only (); + + /** + * @brief Raises an exception with the given string + * This version prints the last checkpoint for reference. + */ + void raise (const std::string &s); + + /** + * @brief Raises an exception with the given string, file and line number + */ + void raise (const std::string &file, int line, const std::string &s); + + /** + * @brief Registers a checkpoint + */ + void checkpoint (const std::string &file, int line); + + /** + * @brief Resets the checkpoints set + */ + void reset_checkpoint (); + + /** + * @brief Compares a layout with a golden layout file + * @param layout The layout to compare + * @param au_file The golden file path + * @param norm The normalization mode (see NormalizationMode) + * @param tolerance A tolerance applied when comparing shapes in database units + * The layout is normalized by writing to a file and reading back + */ + void compare_layouts (const db::Layout &layout, const std::string &au_file, NormalizationMode norm = WriteGDS2, db::Coord tolerance = 0); + + /** + * @brief Compares a layout with a golden layout file with layer mapping + * @param layout The layout to compare + * @param au_file The golden file path + * @param lmap The layer mapping object + * @param read_all_others If true, all other layers will be read too + * @param norm The normalization mode (see NormalizationMode) + * @param tolerance A tolerance applied when comparing shapes in database units + * The layout is normalized by writing to a file and reading back + */ + void compare_layouts (const db::Layout &layout, const std::string &au_file, const db::LayerMap &lmap, bool read_all_others, NormalizationMode norm = WriteGDS2, db::Coord tolerance = 0); + + /** + * @brief Compares two text files + */ + void compare_text_files (const std::string &path_a, const std::string &path_b); + + /** + * @brief The test's name + * @return The name of the test + */ + const std::string &name () const + { + return m_test; + } + + /** + * @brief Prepares a temporary file path + * @param fn The actual name of the file + * @return A path suitable for writing a temporary file + * The directory for the file will be created within this method. + */ + std::string tmp_file (const std::string &fn = "tmp") const; + + /** + * @brief Removes all temporary files + */ + void remove_tmp_folder (); + + /** + * @brief A generic diff printer + */ + template + void diff (const std::string &file, int line, const std::string &msg, const X &subject, const Y & /*ref*/) + { + std::ostringstream sstr; + sstr << msg << " (actual value is " << subject << ")"; + raise (file, line, sstr.str ()); + } + + /** + * @brief A generic diff printer + */ + template + void detailed_diff (const std::string &file, int line, const std::string &msg, const X &subject, const Y &ref) + { + std::ostringstream sstr; + sstr << msg << std::endl; + write_detailed_diff (sstr, tl::to_string (subject), tl::to_string (ref)); + raise (file, line, sstr.str ()); + } + + /** + * @brief A diff printer for int vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, int subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for unsigned int vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, unsigned int subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for long vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, long subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for unsigned long vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, unsigned long subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for long long vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, long long subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for unsigned long long vs. something + */ + template + void diff (const std::string &file, int line, const std::string &msg, unsigned long long subject, const Y &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for bool + */ + inline void diff (const std::string &file, int line, const std::string &msg, bool subject, bool ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for double + */ + inline void diff (const std::string &file, int line, const std::string &msg, double subject, double ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for strings + */ + inline void diff (const std::string &file, int line, const std::string &msg, const std::string &subject, const std::string &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for strings vs. const char * + */ + inline void diff (const std::string &file, int line, const std::string &msg, const std::string &subject, const char *ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for strings vs. const char * + */ + inline void diff (const std::string &file, int line, const std::string &msg, const char *subject, const std::string &ref) + { + detailed_diff (file, line, msg, subject, ref); + } + + /** + * @brief A diff printer for C strings + */ + inline void diff (const std::string &file, int line, const std::string &msg, const char *subject, const char *ref) + { + diff (file, line, msg, std::string (subject), std::string (ref)); + } + + /** + * @brief Main entry point for the compare feature (EXPECT_EQ and EXPECT_NE) + */ + template + void eq_helper (bool eq, const T1 &a, const T2 &b, const char *what_expr, const char *equals_expr, const char *file, int line) + { + if (ut::equals (a, b) != eq) { + std::ostringstream sstr; + sstr << what_expr << " does not equal " << equals_expr; + diff (file, line, sstr.str (), a, b); + } + } + +private: + virtual void execute (ut::TestBase *_this) throw (tl::Exception) = 0; + + void write_detailed_diff (std::ostream &os, const std::string &subject, const std::string &ref); + + bool m_editable, m_slow; + std::string m_test; + std::string m_testdir; + // last checkpoint + std::string m_cp_file; + int m_cp_line; + bool m_any_failed; + QString m_testtmp; +}; + +/** + * @brief The registration facility for tests + */ +struct Registrar +{ + static void reg (ut::TestBase *t) + { + if (! ms_instance) { + ms_instance = new Registrar (); + } + ms_instance->m_tests.push_back (t); + } + + static Registrar *instance () + { + return ms_instance; + } + + const std::vector &tests () const + { + return m_tests; + } + +private: + static Registrar *ms_instance; + + Registrar () : m_tests () { } + + std::vector m_tests; +}; + +} + +#endif + diff --git a/src/ut/utTestConsole.cc b/src/ut/utTestConsole.cc new file mode 100644 index 000000000..fa47cdcbe --- /dev/null +++ b/src/ut/utTestConsole.cc @@ -0,0 +1,412 @@ + +/* + + 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 "utTestConsole.h" +#include "utHead.h" + +#include "rba.h" +#include "pya.h" + +#include + +#if !defined(_WIN32) +# include +# include +#endif +#if defined(_WIN32) +# include +#endif + +namespace ut +{ + +// ------------------------------------------------ +// CaptureChannel implementation + +CaptureChannel::CaptureChannel () +{ + tl::info.add (this, false); + tl::error.add (this, false); + tl::warn.add (this, false); +} + +void CaptureChannel::puts (const char *s) +{ + m_text << s; +} + +void CaptureChannel::endl () +{ + m_text << "\n"; +} + +void CaptureChannel::end () +{ + // .. nothing yet .. +} + +void CaptureChannel::begin () +{ + // .. nothing yet .. +} + +// ------------------------------------------------ +// tl::Channel implementations for redirecting the log output + +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 (); + } +}; + +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; +}; + +// ------------------------------------------------ +// TestConsole implementation + +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"; + +TestConsole::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 + + redirect (); +} + +TestConsole::~TestConsole () +{ + restore (); + + if (ms_instance == this) { + ms_instance = 0; + } +} + +void +TestConsole::write_str (const char *text, output_stream os) +{ + if (os == OS_stderr) { + begin_error (); + basic_write (text); + end (); + } else { + basic_write (text); + } +} + +void +TestConsole::raw_write (const char *text) +{ + fputs (text, m_file); +} + +void +TestConsole::flush () +{ + fflush (m_file); +} + +void +TestConsole::begin_error () +{ + if (m_is_tty) { + fputs (ANSI_RED, m_file); + } +} + +void +TestConsole::begin_info () +{ + if (m_is_tty) { + fputs (ANSI_GREEN, m_file); + } +} + +void +TestConsole::begin_warn () +{ + if (m_is_tty) { + fputs (ANSI_BLUE, m_file); + } +} + +void +TestConsole::end () +{ + if (m_is_tty) { + fputs (ANSI_RESET, m_file); + } +} + +void +TestConsole::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; + } + } + } + + } +} + +void +TestConsole::redirect () +{ + // 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); + + ut::ruby_interpreter ()->push_console (this); + ut::python_interpreter ()->push_console (this); +} + +void +TestConsole::restore () +{ + // TODO: we should basically restore the original channels + tl::warn.clear (); + tl::info.clear (); + tl::log.clear (); + tl::error.clear (); + + ut::ruby_interpreter ()->remove_console (this); + ut::python_interpreter ()->remove_console (this); +} + +TestConsole *TestConsole::ms_instance = 0; + +tl::LogTee noctrl (new CtrlChannel (false), true); +tl::LogTee ctrl (new CtrlChannel (true), true); + + + +} diff --git a/src/ut/utTestConsole.h b/src/ut/utTestConsole.h new file mode 100644 index 000000000..a3d700ece --- /dev/null +++ b/src/ut/utTestConsole.h @@ -0,0 +1,141 @@ + +/* + + 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 + +*/ + + +#ifndef HDR_utTestConsole +#define HDR_utTestConsole + +#include "utCommon.h" +#include "tlLog.h" +#include "gsiInterpreter.h" + +#include +#include + +namespace ut +{ + +/** + * @brief A utility class to capture the warning, error and info channels + * + * Instantiate this class inside a test. Then run the test and finally + * obtain the collected output with CaptureChannel::captured_text(). + */ +class UT_PUBLIC CaptureChannel : public tl::Channel +{ +public: + CaptureChannel (); + + std::string captured_text () const + { + return m_text.str (); + } + + void clear () + { + m_text.str (std::string ()); + } + +protected: + virtual void puts (const char *s); + virtual void endl (); + virtual void end (); + virtual void begin (); + +private: + std::ostringstream m_text; +}; + +/** + * @brief Redirects the interpreter output and serves as a general output device + */ +class TestConsole + : public gsi::Console +{ +public: + static TestConsole *instance () + { + tl_assert (ms_instance != 0); + return ms_instance; + } + + TestConsole (FILE *file, bool xml_format); + ~TestConsole (); + + int indent () const + { + return m_indent; + } + + bool xml_format () const + { + return m_xml_format; + } + + void write_str (const char *text, output_stream os); + void raw_write (const char *text); + virtual void flush (); + + 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 (); + void begin_info (); + void begin_warn (); + void end (); + void basic_write (const char *s); + +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; + + void redirect (); + void restore (); +}; + +} + +#endif diff --git a/testdata/drc/drcSimpleTests_1.drc b/testdata/drc/drcSimpleTests_1.drc new file mode 100644 index 000000000..75b575701 --- /dev/null +++ b/testdata/drc/drcSimpleTests_1.drc @@ -0,0 +1,21 @@ + +dbu 0.001 + +target($drc_test_target, "TOP") + +x = polygon_layer +x.is_empty? == true || raise("unexpected value") +x.is_box? == false || raise("unexpected value") +x.insert(box(4.0, 0, 4.7, 0.7)) +x.is_empty? == false || raise("unexpected value") +x.is_box? == true || raise("unexpected value") +x.insert(polygon([ p(0, 0), p(2.0, 0), p(1.0, 1.0) ])) +x.insert(polygon([ p(0, -5.0), p(2.0, -5.0), p(1.0, -6.0) ])) +x.insert(path([ p(0, -2), p(2.0, -2) ], 0.2)) +x.is_box? == false || raise("unexpected value") +x.output(10, 0) + +y = polygon_layer +y.insert(polygon([ p(0, 0), p(0, 1.0), p(2.0, 1.0), p(2.0, 2.0), p(1.0, 2.0), p(1.0, 0) ])) +y.output(11, 0) + diff --git a/testdata/drc/drcSimpleTests_2.drc b/testdata/drc/drcSimpleTests_2.drc new file mode 100644 index 000000000..5582c41dd --- /dev/null +++ b/testdata/drc/drcSimpleTests_2.drc @@ -0,0 +1,90 @@ + +target($drc_test_target, "TOP") +source($drc_test_source, "TOP") + +a1 = input(1) +b1 = input(2) +c1 = input(3) + +a1.output(1, 0) +b1.output(2, 0) +c1.output(3, 0) + +c1.rounded_corners(0.5, 0.5, 16).output(1010, 0) +c1.smoothed(1.5).output(1011, 0) + +a1.texts.output(1020, 0) +a1.texts("A*").output(1021, 0) +a1.texts(text("A*")).output(1022, 0) +a1.texts(pattern("A*")).output(1023, 0) +a1.texts(pattern("A*"), as_dots).extended(0.05, 0.05, 0.05, 0.05).output(1024, 0) +a1.texts(pattern("A*"), as_boxes).output(1025, 0) + +a1.middle.sized(0.05).output(1030, 0) +a1.middle(as_dots).extended(0.05, 0.05, 0.05, 0.05).output(1031, 0) +a1.middle(as_boxes).sized(0.05).output(1032, 0) +a1.extent_refs(0.5, 0.5).sized(0.05).output(1040, 0) +a1.extent_refs(:center).sized(0.05).output(1040, 1) +a1.extent_refs(:c).sized(0.05).output(1040, 2) +a1.extent_refs(:bottom).sized(0.05).output(1041, 0) +a1.extent_refs(:b).sized(0.05).output(1041, 1) +a1.extent_refs(:b, as_edges).extended(0.05, 0.05, 0.05, 0.05).output(1041, 2) +a1.extent_refs(:top).sized(0.05).output(1042, 0) +a1.extent_refs(:t).sized(0.05).output(1042, 1) +a1.extent_refs(:left).sized(0.05).output(1043, 0) +a1.extent_refs(:l).sized(0.05).output(1043, 1) +a1.extent_refs(:right).sized(0.05).output(1044, 0) +a1.extent_refs(:r).sized(0.05).output(1044, 1) +a1.extent_refs(:bottom_left).sized(0.05).output(1045, 0) +a1.extent_refs(:bl).sized(0.05).output(1045, 1) +a1.extent_refs(:bottom_center).sized(0.05).output(1046, 0) +a1.extent_refs(:bc).sized(0.05).output(1046, 1) +a1.extent_refs(:bottom_right).sized(0.05).output(1047, 0) +a1.extent_refs(:br).sized(0.05).output(1047, 1) +a1.extent_refs(:top_left).sized(0.05).output(1048, 0) +a1.extent_refs(:tl).sized(0.05).output(1048, 1) +a1.extent_refs(:top_center).sized(0.05).output(1049, 0) +a1.extent_refs(:tc).sized(0.05).output(1049, 1) +a1.extent_refs(:top_right).sized(0.05).output(1050, 0) +a1.extent_refs(:tr).sized(0.05).output(1050, 1) +a1.extent_refs(:left_center).sized(0.05).output(1051, 0) +a1.extent_refs(:lc).sized(0.05).output(1051, 1) +a1.extent_refs(:right_center).sized(0.05).output(1052, 0) +a1.extent_refs(:rc).sized(0.05).output(1052, 1) +a1.extent_refs(0.25, 0.5, 0.5, 0.75).output(1053, 0) + +a1.corners.sized(0.05).output(1060, 0) +a1.corners(-90.0, as_boxes).sized(0.05).output(1061, 0) +a1.corners(-90.0, as_dots).extended(0.05, 0.05, 0.05, 0.05).output(1062, 0) + +a1.select { |p| p.bbox.width < 0.8 }.output(1100, 0) +a1.collect { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1101, 0) +a1.collect_to_region { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1102, 0) +a1.collect_to_edges { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1103, 0) +a1.collect { |p| p.is_box? && p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(120, 120) }.output(1104, 0) +a1.collect { |p| p.is_box? && [ p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(150, 150), p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(120, 120) ] }.output(1105, 0) +lx = polygon_layer +a1.each { |p| p.is_box? && lx.insert(p) } +lx.output(1106, 0) +a1.collect { |p| p.is_box? && RBA::Region::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))).sized(120) }.output(1107, 0) +a1.collect { |p| p.is_box? && RBA::Polygon::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))) }.output(1108, 0) +a1.collect { |p| p.is_box? && RBA::DPolygon::new(p.bbox) }.output(1109, 0) +a1.collect { |p| p.is_box? && RBA::SimplePolygon::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))) }.output(1110, 0) +a1.collect { |p| p.is_box? && RBA::DSimplePolygon::new(p.bbox) }.output(1111, 0) +a1.collect_to_edges { |p| p.is_box? && p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(120, 120) }.output(1112, 0) +a1.collect_to_edges { |p| p.is_box? && [ p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(150, 150), p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(120, 120) ] }.output(1113, 0) +a1.collect_to_edges { |p| p.is_box? && RBA::Region::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))).sized(120) }.output(1114, 0) +a1.collect_to_edges { |p| p.is_box? && RBA::Polygon::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))) }.output(1115, 0) +a1.collect_to_edges { |p| p.is_box? && RBA::DPolygon::new(p.bbox) }.output(1116, 0) +a1.collect_to_edges { |p| p.is_box? && RBA::SimplePolygon::new(p.bbox.transformed(RBA::VCplxTrans::new(1000.0))) }.output(1117, 0) +a1.collect_to_edges { |p| p.is_box? && RBA::DSimplePolygon::new(p.bbox) }.output(1118, 0) + +a1.edges.select { |p| p.length < 0.8 }.output(1120, 0) +a1.edges.collect { |p| p.length < 0.8 && p.transformed(RBA::VCplxTrans::new(1000.0)) }.output(1121, 0) +a1.edges.collect_to_region { |p| p.length < 0.8 && p.bbox.enlarged(0.1, 0.1) }.output(1122, 0) +a1.edges.collect_to_region { |p| p.length < 0.8 && p.bbox.transformed(RBA::VCplxTrans::new(1000.0)).enlarged(100, 100) }.output(1123, 0) + +# edge pair collect +a1.width(1.5).collect { |p| p.transformed(RBA::VCplxTrans::new(1000.0)) }.output(1120, 0) +a1.width(1.5).collect_to_edge_pairs { |p| p.transformed(RBA::VCplxTrans::new(1000.0)) }.output(1121, 0) + diff --git a/testdata/drc/drcSimpleTests_3.drc b/testdata/drc/drcSimpleTests_3.drc new file mode 100644 index 000000000..510891813 --- /dev/null +++ b/testdata/drc/drcSimpleTests_3.drc @@ -0,0 +1,19 @@ + +# Foreign cell test + +source($drc_test_source, "TOPTOP") +target($drc_test_target) + +cell("TOP") + +a1 = input(1) +b1 = input(2) + +output_cell("NEW") + +a1.and(b1).output(1000, 0) + +cell("TOPTOP") + +a1.and(b1).output(1001, 0) + diff --git a/testdata/drc/drcSimpleTests_au1.gds b/testdata/drc/drcSimpleTests_au1.gds new file mode 100644 index 000000000..6cd7e0683 Binary files /dev/null and b/testdata/drc/drcSimpleTests_au1.gds differ diff --git a/testdata/drc/drcSimpleTests_au2.gds b/testdata/drc/drcSimpleTests_au2.gds new file mode 100644 index 000000000..7f15c2f3d Binary files /dev/null and b/testdata/drc/drcSimpleTests_au2.gds differ diff --git a/testdata/drc/drcSimpleTests_au3.gds b/testdata/drc/drcSimpleTests_au3.gds new file mode 100644 index 000000000..7f496f52f Binary files /dev/null and b/testdata/drc/drcSimpleTests_au3.gds differ diff --git a/testdata/drc/drcSuiteTests.drc b/testdata/drc/drcSuiteTests.drc new file mode 100644 index 000000000..3602a6ca4 --- /dev/null +++ b/testdata/drc/drcSuiteTests.drc @@ -0,0 +1,494 @@ + +def message(m) + $stdout.puts(m) + $stdout.flush +end + +def run_testsuite(dm, ic, tiled = false) + + lb = 100 + + a = input(RBA::LayerInfo::new(1, 0)) + b = input(2) + c = input(3) + x = input(10) + y = input(11) + + h = {} + layers.each { |l| h[l] = true } + h[RBA::LayerInfo::new(1, 0)] || raise("missing layer 1/0 in layers list") + + c.is_merged? == false || raise("unexpected value") + + message "--- general #{lb}" + + l1 = a&b + l1.output(lb, dm) + l1.is_empty? && raise("must not be empty") + + a.and(b).xor(l1).is_empty? || raise("xor not empty") + tiled || (a.and(b).is_merged? == true || raise("unexpected value")) + + a.xor(b).output(RBA::LayerInfo::new(lb + 1, dm)) + a.xor(b).xor(a ^ b).is_empty? || raise("xor not empty") + a.not(b).output(lb + 2, dm) + a.not(b).xor(a - b).is_empty? || raise("xor not empty") + a.or(b).output(lb + 3, dm) + a.or(b).xor(a | b).is_empty? || raise("xor not empty") + + a.join(b).output(lb + 4, dm) + a.join(b).xor(a + b).is_empty? || raise("xor not empty") + + if !tiled + a.join(b).data.size == 16 * ic || raise("unexpected shape count") + end + + c.raw.is_clean? == false || raise("unexpected value") + # c.raw switched the semantics + c.is_clean? == false || raise("unexpected value") + (c.area / ic).to_s == "10.0" || raise("unexpected value") + (c.edges.length / ic).to_s == "18.0" || raise("unexpected value") + (c.perimeter / ic).to_s == "18.0" || raise("unexpected value") + c.clean + c.is_clean? == true || raise("unexpected value") + (c.area / ic).to_s == "9.0" || raise("unexpected value") + (c.edges.length / ic).to_s == "14.0" || raise("unexpected value") + (c.perimeter / ic).to_s == "14.0" || raise("unexpected value") + (c.dup.area / ic).to_s == "9.0" || raise("unexpected value") + (c.raw.clean.area / ic).to_s == "9.0" || raise("unexpected value") + (c.area / ic).to_s == "9.0" || raise("unexpected value") + (c.raw.area / ic).to_s == "10.0" || raise("unexpected value") + c.clean + + if ic == 1 + c.bbox.to_s == "(-4,-5;0,-2)" || raise("unexpected value") + end + + lb += 10 + message "--- centers, start_segments, end_segments #{lb}" + + a.edges.centers(0.1).extended_out(0.01).output(lb, dm) + a.edges.centers(0, 0.5).extended_out(0.01).output(lb + 1, dm) + a.edges.centers(0.5, 0.5).extended_out(0.01).output(lb + 2, dm) + a.edges.start_segments(0.1).extended_out(0.01).output(lb + 3, dm) + a.edges.start_segments(0, 0.5).extended_out(0.01).output(lb + 4, dm) + a.edges.start_segments(0.5, 0.5).extended_out(0.01).output(lb + 5, dm) + a.edges.end_segments(0.1).extended_out(0.01).output(lb + 6, dm) + a.edges.end_segments(0, 0.5).extended_out(0.01).output(lb + 7, dm) + a.edges.end_segments(0.5, 0.5).extended_out(0.01).output(lb + 8, dm) + + a.polygons? == true || raise("unexpected value") + a.edges? == false || raise("unexpected value") + a.edge_pairs? == false || raise("unexpected value") + a.edges.edge_pairs? == false || raise("unexpected value") + a.edges.polygons? == false || raise("unexpected value") + a.edges.edges? == true || raise("unexpected value") + a.space(0.5).edge_pairs? == true || raise("unexpected value") + a.space(0.5).polygons? == false || raise("unexpected value") + a.space(0.5).edges? == false || raise("unexpected value") + + lb += 10 + message "--- extended #{lb}" + + a.edges.with_length(0.6).extended(begin: 0.1, end: 0.15, out: 0.1, in: -0.05).output(lb, dm) + a.edges.with_length(0.6).extended(out: 0.1, in: -0.05).output(lb + 1, dm) + a.edges.with_length(0..0.7).extended(out: 0.1, in: -0.05).output(lb + 2, dm) + a.edges.with_length(0..0.7).extended(out: 0.1, in: -0.05, joined: true).output(lb + 3, dm) + a.edges.with_length(0..0.7).extended(begin: 0.1, end: 0.15, out: 0.1, in: 0.05, joined: true).output(lb + 4, dm) + a.edges.with_length(0..0.7).extended(0.1, 0.15, 0.1, 0.05).output(lb + 5, dm) + a.edges.with_length(0..0.7).extended(0.1, 0.15, 0.1, 0.05, true).output(lb + 6, dm) + a.edges.with_length(0..0.7).extended_out(0.1).output(lb + 7, dm) + a.edges.with_length(0..0.7).extended_in(0.1).output(lb + 8, dm) + + lb += 10 + message "--- extents #{lb}" + + b.extents.output(lb, dm) + + lb += 10 + message "--- first_edges, second_edges, edges #{lb}" + + a.enclosing(b, 0.5, whole_edges).first_edges.extended_out(0.01).output(lb, dm) + a.enclosing(b, 0.5, whole_edges).second_edges.extended_out(0.01).output(lb + 1, dm) + a.enclosing(b, 0.5, whole_edges).edges.extended_out(0.01).output(lb + 2, dm) + + lb += 10 + message "--- hulls, holes #{lb}" + + c.xor(b).holes.output(lb, dm) + c.xor(b).hulls.output(lb + 1, dm) + c.xor(b).xor(c.xor(b).hulls - c.xor(b).holes).is_empty? || raise("xor not empty") + + lb += 10 + message "--- interacting, in, not_in #{lb}" + + b.interacting(a).output(lb, dm) + b.interacting(a).in(b).output(lb + 1, dm) + (b|a).not_in(b).output(lb + 2, dm) + x.in(b).output(lb + 3, dm) + b.sized(0.1).in(b).is_empty? == true || raise("unexpected value") + + lb += 10 + message "--- inside, outside, overlapping, interacting #{lb}" + + b.inside(c).output(lb, dm) + b.outside(c).output(lb + 1, dm) + b.overlapping(c).output(lb + 2, dm) + b.interacting(c).output(lb + 3, dm) + bdup = b.dup + bdup.select_inside(c) + bdup.xor(b.inside(c)).output(lb + 4, dm) + bdup = b.dup + bdup.select_outside(c) + bdup.xor(b.outside(c)).output(lb + 5, dm) + bdup = b.dup + bdup.select_overlapping(c) + bdup.xor(b.overlapping(c)).output(lb + 6, dm) + bdup = b.dup + bdup.select_interacting(c) + bdup.xor(b.interacting(c)).output(lb + 7, dm) + + lb += 10 + message "--- merge #{lb}" + + c.raw + if !tiled + c.merged.is_merged? == true || raise("unexpected value") + end + c.merged.output(lb, dm) + c.merged(2).output(lb + 1, dm) + c.merged(3).output(lb + 2, dm) + c.clean + cdup = c.dup + cdup.merge.xor(c.merged).output(lb + 3, dm) + cdup.xor(c.merged).output(lb + 4, dm) + cdup = c.dup + cdup.merge(2).xor(c.merged(2)).output(lb + 5, dm) + cdup.xor(c.merged(2)).output(lb + 6, dm) + + lb += 10 + message "--- move, transform, rotate, scale #{lb}" + + c.moved(0.2, -0.1).output(lb, dm) + cdup = c.dup + cdup.move(0.2, -0.1) + cdup.xor(c.moved(0.2, -0.1)).is_empty? == true || raise("xor not empty") + t = RBA::DCplxTrans::new(0.5) + c.transformed(t).output(lb + 1, dm) + cdup = c.dup + cdup.transform(t) + cdup.xor(c.transformed(t)).is_empty? == true || raise("xor not empty") + c.scaled(0.5).output(lb + 2, dm) + cdup = c.dup + cdup.scale(0.5) + cdup.xor(c.scaled(0.5)).is_empty? == true || raise("xor not empty") + c.rotated(45).output(lb + 3, dm) + cdup = c.dup + cdup.rotate(45) + cdup.xor(c.rotated(45)).is_empty? == true || raise("xor not empty") + + lb += 10 + message "--- rectangles, rectilinear #{lb}" + + a.rectangles.output(lb, dm) + a.non_rectangles.output(lb + 1, dm) + a.rectilinear.output(lb + 2, dm) + a.non_rectilinear.output(lb + 3, dm) + c.raw + c.non_rectangles.output(lb + 4, dm) + c.clean + c.rectangles.output(lb + 5, dm) + + lb += 10 + message "--- enclosing #{lb}" + + a.enclosing(b, 0.5).polygons.output(lb, dm) + a.enclosing(b, 0.5).polygons.xor(a.enc(b, 0.5).polygons).is_empty? == true || raise("xor not empty") + a.enclosing(b, 0.5, euclidian).polygons.output(lb + 1, dm) + a.enclosing(b, 0.5, square).polygons.output(lb + 2, dm) + a.enclosing(b, 0.5, projection).polygons.output(lb + 3, dm) + a.enclosing(b, 0.5, euclidian, whole_edges).polygons.output(lb + 4, dm) + a.enclosing(b, 0.5, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + a.enclosing(b, 0.5, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + a.enclosing(b, 0.5, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- enclosing (edges) #{lb}" + + ae = a.edges + be = b.edges + ae.enclosing(be, 0.5).polygons.output(lb, dm) + ae.enclosing(be, 0.5).polygons.xor(ae.enc(be, 0.5).polygons).is_empty? == true || raise("xor not empty") + ae.enclosing(be, 0.5, euclidian).polygons.output(lb + 1, dm) + ae.enclosing(be, 0.5, square).polygons.output(lb + 2, dm) + ae.enclosing(be, 0.5, projection).polygons.output(lb + 3, dm) + ae.enclosing(be, 0.5, euclidian, whole_edges).polygons.output(lb + 4, dm) + ae.enclosing(be, 0.5, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + ae.enclosing(be, 0.5, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + ae.enclosing(be, 0.5, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- isolated #{lb}" + + b.isolated(0.4).polygons.output(lb, dm) + b.isolated(0.4).polygons.xor(b.iso(0.4).polygons).is_empty? == true || raise("xor not empty") + b.isolated(0.4, euclidian).polygons.output(lb + 1, dm) + b.isolated(0.4, square).polygons.output(lb + 2, dm) + b.isolated(0.4, projection).polygons.output(lb + 3, dm) + b.isolated(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + b.isolated(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + b.isolated(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + b.isolated(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- notch #{lb}" + + b.notch(0.4).polygons.output(lb, dm) + b.notch(0.4, euclidian).polygons.output(lb + 1, dm) + b.notch(0.4, square).polygons.output(lb + 2, dm) + b.notch(0.4, projection).polygons.output(lb + 3, dm) + b.notch(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + b.notch(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + b.notch(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + b.notch(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- overlap #{lb}" + + a.overlap(b, 0.5).polygons.output(lb, dm) + a.overlap(b, 0.5, euclidian).polygons.output(lb + 1, dm) + a.overlap(b, 0.5, square).polygons.output(lb + 2, dm) + a.overlap(b, 0.5, projection).polygons.output(lb + 3, dm) + a.overlap(b, 0.5, euclidian, whole_edges).polygons.output(lb + 4, dm) + a.overlap(b, 0.5, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + a.overlap(b, 0.5, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + a.overlap(b, 0.5, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- overlap (edges) #{lb}" + + ae = a.edges + be = b.edges + ae.overlap(be, 0.5).polygons.output(lb, dm) + ae.overlap(be, 0.5, euclidian).polygons.output(lb + 1, dm) + ae.overlap(be, 0.5, square).polygons.output(lb + 2, dm) + ae.overlap(be, 0.5, projection).polygons.output(lb + 3, dm) + ae.overlap(be, 0.5, euclidian, whole_edges).polygons.output(lb + 4, dm) + ae.overlap(be, 0.5, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + ae.overlap(be, 0.5, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + ae.overlap(be, 0.5, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- separation #{lb}" + + a.separation(b, 0.4).polygons.output(lb, dm) + a.separation(b, 0.4).polygons.xor(a.sep(b, 0.4).polygons).is_empty? == true || raise("xor not empty") + a.separation(b, 0.4, euclidian).polygons.output(lb + 1, dm) + a.separation(b, 0.4, square).polygons.output(lb + 2, dm) + a.separation(b, 0.4, projection).polygons.output(lb + 3, dm) + a.separation(b, 0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + a.separation(b, 0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + a.separation(b, 0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + a.separation(b, 0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- sized #{lb}" + + x.sized(0.1).output(lb, dm) + x.sized(0.1, 0).output(lb + 1, dm) + x.sized(0.1, diamond_limit).output(lb + 2, dm) + x.sized(0.1, octagon_limit).output(lb + 3, dm) + x.sized(0.1, square_limit).output(lb + 4, dm) + x.sized(0.1, acute_limit).output(lb + 5, dm) + x.sized(0.1, no_limit).output(lb + 6, dm) + xdup = x.dup + xdup.size(0.1, 0) + xdup.xor(x.sized(0.1, 0)).is_empty? == true || raise("xor not empty") + a.sized(4.0, no_limit).output(lb + 7, dm) + + lb += 10 + message "--- space #{lb}" + + b.space(0.4).polygons.output(lb, dm) + b.space(0.4, euclidian).polygons.output(lb + 1, dm) + b.space(0.4, square).polygons.output(lb + 2, dm) + b.space(0.4, projection).polygons.output(lb + 3, dm) + b.space(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + b.space(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + b.space(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + b.space(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- space (edges) #{lb}" + + be = b.edges + be.space(0.4).polygons.output(lb, dm) + be.space(0.4, euclidian).polygons.output(lb + 1, dm) + be.space(0.4, square).polygons.output(lb + 2, dm) + be.space(0.4, projection).polygons.output(lb + 3, dm) + be.space(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + be.space(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + be.space(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + be.space(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- width #{lb}" + + b.width(0.4).polygons.output(lb, dm) + b.width(0.4, euclidian).polygons.output(lb + 1, dm) + b.width(0.4, square).polygons.output(lb + 2, dm) + b.width(0.4, projection).polygons.output(lb + 3, dm) + b.width(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + b.width(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + b.width(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + b.width(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- width (edges) #{lb}" + + be = b.edges + be.width(0.4).polygons.output(lb, dm) + be.width(0.4, euclidian).polygons.output(lb + 1, dm) + be.width(0.4, square).polygons.output(lb + 2, dm) + be.width(0.4, projection).polygons.output(lb + 3, dm) + be.width(0.4, euclidian, whole_edges).polygons.output(lb + 4, dm) + be.width(0.4, euclidian, projection_limits(0.4, nil)).polygons.output(lb + 5, dm) + be.width(0.4, euclidian, projection_limits(nil, 0.4)).polygons.output(lb + 6, dm) + be.width(0.4, euclidian, projection_limits(0..0.4)).polygons.output(lb + 7, dm) + + lb += 10 + message "--- with_angle #{lb}" + + a.edges.with_angle(90.degree).extended_out(0.01).output(lb, dm) + a.edges.with_angle(45 .. 90.1).extended_out(0.01).output(lb + 1, dm) + a.edges.with_angle(0, 45.1).extended_out(0.01).output(lb + 2, dm) + a.edges.without_angle(90.degree).extended_out(0.01).output(lb + 3, dm) + a.edges.without_angle(45 .. 90.1).extended_out(0.01).output(lb + 4, dm) + a.edges.without_angle(0, 45.1).extended_out(0.01).output(lb + 5, dm) + a.with_angle(135).polygons.output(lb + 6, dm) + + lb += 10 + message "--- with_area #{lb}" + + b.with_area(0.49.um2).output(lb, dm) + b.with_area(0 .. 0.5).output(lb + 1, dm) + b.with_area(0.5, nil).output(lb + 2, dm) + b.without_area(0.49.um2).output(lb + 3, dm) + b.without_area(nil, 0.5).output(lb + 4, dm) + b.without_area(0.5, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_perimeter #{lb}" + + b.with_perimeter(2.8.um).output(lb, dm) + b.with_perimeter(0 .. 3.um).output(lb + 1, dm) + b.with_perimeter(3.um, nil).output(lb + 2, dm) + b.without_perimeter(2.8.um).output(lb + 3, dm) + b.without_perimeter(nil, 3.um).output(lb + 4, dm) + b.without_perimeter(3.um, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_bbox_height #{lb}" + + b.with_bbox_height(0.7.um).output(lb, dm) + b.with_bbox_height(0 .. 0.7.um+1.dbu).output(lb + 1, dm) + b.with_bbox_height(0.7.um, nil).output(lb + 2, dm) + b.without_bbox_height(0.7.um).output(lb + 3, dm) + b.without_bbox_height(nil, 0.7.um+1.dbu).output(lb + 4, dm) + b.without_bbox_height(0.7.um, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_bbox_width #{lb}" + + b.with_bbox_width(0.7.um).output(lb, dm) + b.with_bbox_width(0 .. 0.7.um+1.dbu).output(lb + 1, dm) + b.with_bbox_width(0.7.um, nil).output(lb + 2, dm) + b.without_bbox_width(0.7.um).output(lb + 3, dm) + b.without_bbox_width(nil, 0.7.um+1.dbu).output(lb + 4, dm) + b.without_bbox_width(0.7.um, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_bbox_min #{lb}" + + b.with_bbox_min(0.7.um).output(lb, dm) + b.with_bbox_min(0 .. 0.7.um+1.dbu).output(lb + 1, dm) + b.with_bbox_min(0.7.um, nil).output(lb + 2, dm) + b.without_bbox_min(0.7.um).output(lb + 3, dm) + b.without_bbox_min(nil, 0.7.um+1.dbu).output(lb + 4, dm) + b.without_bbox_min(0.7.um, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_bbox_max #{lb}" + + b.with_bbox_max(0.7.um).output(lb, dm) + b.with_bbox_max(0 .. 0.7.um+1.dbu).output(lb + 1, dm) + b.with_bbox_max(0.7.um, nil).output(lb + 2, dm) + b.without_bbox_max(0.7.um).output(lb + 3, dm) + b.without_bbox_max(nil, 0.7.um+1.dbu).output(lb + 4, dm) + b.without_bbox_max(0.7.um, nil).output(lb + 5, dm) + + lb += 10 + message "--- with_length #{lb}" + + a.edges.with_length(0.6.um).extended_out(0.01).output(lb, dm) + a.edges.with_length(0 .. 0.6.um+1.dbu).extended_out(0.01).output(lb + 1, dm) + a.edges.with_length(0.6, nil).extended_out(0.01).output(lb + 2, dm) + a.edges.without_length(0.6.um).extended_out(0.01).output(lb + 3, dm) + a.edges.without_length(0 .. 0.6.um+1.dbu).extended_out(0.01).output(lb + 4, dm) + a.edges.without_length(0.6, nil).extended_out(0.01).output(lb + 5, dm) + + lb += 10 + message "--- ongrid, snap #{lb}" + + a.ongrid(0.2).polygons(1.dbu).output(lb, dm) + a.ongrid(0, 0.1).polygons(1.dbu).output(lb + 1, dm) + a.snapped(0.2).output(lb + 2, dm) + a.snapped(0, 0.1).output(lb + 3, dm) + adup = a.dup + adup.snap(0.2) + adup.xor(a.snapped(0.2)).output(lb + 4, dm) + + lb += 10 + message "--- odd polygon check #{lb}" + + y.odd_polygons.output(lb, dm) + +end + +if $drc_test_mode == 1 + + source($drc_test_source, "TOP") + target($drc_test_target, "TOP") + run_testsuite(0, 1) + +elsif $drc_test_mode == 2 + + target($drc_test_target, "TOPTOP") + source($drc_test_source, "TOPTOP") + run_testsuite(0, 900) + +elsif $drc_test_mode == 3 + + target($drc_test_target, "TOPTOP") + source($drc_test_source, "TOPTOP") + + tiles(4.1, 5.2) + # must be that big ... + tile_borders(1.um, 1.um) + threads(4) + + run_testsuite(0, 900, true) + +elsif $drc_test_mode == 4 + + target($drc_test_target, "TOPTOP") + source($drc_test_source, "TOPTOP") + + tiles(10000.0, 10000.0) + tile_borders(0, 0) + threads(4) + + run_testsuite(0, 900, true) + +end + diff --git a/testdata/drc/drcSuiteTests_au1.gds b/testdata/drc/drcSuiteTests_au1.gds new file mode 100644 index 000000000..5cb92178e Binary files /dev/null and b/testdata/drc/drcSuiteTests_au1.gds differ diff --git a/testdata/drc/drcSuiteTests_au2.gds b/testdata/drc/drcSuiteTests_au2.gds new file mode 100644 index 000000000..e59e510e5 Binary files /dev/null and b/testdata/drc/drcSuiteTests_au2.gds differ diff --git a/testdata/drc/drcSuiteTests_au3.gds b/testdata/drc/drcSuiteTests_au3.gds new file mode 100644 index 000000000..9a4beda7e Binary files /dev/null and b/testdata/drc/drcSuiteTests_au3.gds differ diff --git a/testdata/drc/drcSuiteTests_au4.gds b/testdata/drc/drcSuiteTests_au4.gds new file mode 100644 index 000000000..e59e510e5 Binary files /dev/null and b/testdata/drc/drcSuiteTests_au4.gds differ