diff --git a/.gitignore b/.gitignore index 27d5b19..0991b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ build dist fatamorgana.egg-info docs + +*.gds +*.gds.gz +*.oas +*.oas.gz diff --git a/fatamorgana/test/__init__.py b/fatamorgana/test/__init__.py new file mode 100644 index 0000000..b2eff33 --- /dev/null +++ b/fatamorgana/test/__init__.py @@ -0,0 +1,7 @@ +""" +Tests (run with `python3 -m pytest -rxPXs | tee results.txt`) + + + The test_files_* modules are meant to mimic the test cases used by KLayout, +in order to provide a secondary validation mechanism. +""" diff --git a/fatamorgana/test/build_testfiles.py b/fatamorgana/test/build_testfiles.py new file mode 100644 index 0000000..0dedfc4 --- /dev/null +++ b/fatamorgana/test/build_testfiles.py @@ -0,0 +1,96 @@ +''' +Build files equivalent to the test cases used by KLayout. +''' +# type: ignore + +from typing import Callable +from io import BufferedIOBase + + +from . import ( + test_files_properties, test_files_cblocks, test_files_layernames, + test_files_circles, test_files_ctrapezoids, test_files_trapezoids, + test_files_placements, test_files_paths, test_files_modals, + test_files_polygons, test_files_rectangles, test_files_empty, + test_files_texts, test_files_cells) + + +def build_file(num: str, func: Callable[[BufferedIOBase], BufferedIOBase]) -> None: + with open('t' + num + '.oas', 'wb') as f: + func(f) + + +def write_all_files() -> None: + build_file('1.1', test_files_empty.write_file_1) + build_file('1.2', test_files_empty.write_file_2) + build_file('1.3', test_files_empty.write_file_3) + build_file('1.4', test_files_empty.write_file_4) + build_file('1.5', test_files_empty.write_file_5) + + build_file('2.1', test_files_cells.write_file_1) + build_file('2.2', test_files_cells.write_file_2) + build_file('2.3', test_files_cells.write_file_3) + build_file('2.4', test_files_cells.write_file_4) + build_file('2.5', test_files_cells.write_file_5) + build_file('2.6', test_files_cells.write_file_6) + build_file('2.7', test_files_cells.write_file_7) + + build_file('3.1', lambda f: test_files_texts.write_file_common(f, 1)) + build_file('3.2', lambda f: test_files_texts.write_file_common(f, 2)) + build_file('3.3', test_files_texts.write_file_3) + build_file('3.4', test_files_texts.write_file_4) + build_file('3.5', lambda f: test_files_texts.write_file_common(f, 5)) + build_file('3.6', test_files_texts.write_file_6) + build_file('3.7', test_files_texts.write_file_7) + build_file('3.8', test_files_texts.write_file_8) + build_file('3.9', test_files_texts.write_file_9) + build_file('3.10', test_files_texts.write_file_10) + build_file('3.11', test_files_texts.write_file_11) + + build_file('4.1', lambda f: test_files_rectangles.write_file_common(f, 1)) + build_file('4.2', lambda f: test_files_rectangles.write_file_common(f, 2)) + + build_file('5.1', lambda f: test_files_polygons.write_file_common(f, 1)) + build_file('5.2', test_files_polygons.write_file_2) + build_file('5.3', lambda f: test_files_polygons.write_file_common(f, 3)) + + build_file('6.1', test_files_paths.write_file_1) + + build_file('7.1', test_files_trapezoids.write_file_1) + + build_file('8.1', test_files_placements.write_file_1) + build_file('8.2', lambda f: test_files_placements.write_file_common(f, 2)) + build_file('8.3', lambda f: test_files_placements.write_file_common(f, 3)) + build_file('8.4', test_files_placements.write_file_4) + build_file('8.5', lambda f: test_files_placements.write_file_common(f, 5)) + build_file('8.6', test_files_placements.write_file_6) + build_file('8.7', lambda f: test_files_placements.write_file_common(f, 7)) + build_file('8.8', test_files_placements.write_file_8) + + build_file('9.1', test_files_ctrapezoids.write_file_1) + build_file('9.2', test_files_ctrapezoids.write_file_2) + + build_file('10.1', test_files_modals.write_file_1) + + build_file('11.1', lambda f: test_files_properties.write_file_common(f, 1)) + build_file('11.2', lambda f: test_files_properties.write_file_common(f, 2)) + build_file('11.3', test_files_properties.write_file_3) + build_file('11.4', lambda f: test_files_properties.write_file_4_6(f, 4)) + build_file('11.5', lambda f: test_files_properties.write_file_common(f, 5)) + build_file('11.6', lambda f: test_files_properties.write_file_4_6(f, 6)) + build_file('11.7', lambda f: test_files_properties.write_file_7_8_9(f, 7)) + build_file('11.8', lambda f: test_files_properties.write_file_7_8_9(f, 8)) + build_file('11.9', lambda f: test_files_properties.write_file_7_8_9(f, 9)) + + build_file('12.1', test_files_circles.write_file_1) + + build_file('13.1', test_files_layernames.write_file_1) + build_file('13.2', test_files_layernames.write_file_2) + build_file('13.3', test_files_layernames.write_file_3) + build_file('13.4', test_files_layernames.write_file_4) + + build_file('14.1', test_files_cblocks.write_file_1) + + +if __name__ == '__main__': + write_all_files() diff --git a/fatamorgana/test/test_files_cblocks.py b/fatamorgana/test/test_files_cblocks.py new file mode 100644 index 0000000..93ff0ac --- /dev/null +++ b/fatamorgana/test/test_files_cblocks.py @@ -0,0 +1,183 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABCDH' + assert not layout.cells[0].properties + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABCDH') # Cell name + + cblock_data = bytes.fromhex(''' + 22 00 b0 02 b2 02 13 + a9 66 60 98 c3 32 89 e5 0e e3 1b 61 91 4a c6 15 + ac 8f 58 3a f8 be f0 8a 5a b0 30 57 5f 64 6d e4 + 4f bd c8 7a 87 ed 81 f8 02 79 a0 88 68 f5 42 b6 + 4e be 80 99 4c 3b 99 35 97 30 4f 14 d7 3c 14 f4 + 52 50 e4 24 e3 0b f6 9b c2 1a 9a 27 18 57 4b 8a + 04 ae 65 3f 12 04 24 36 0b 8b 2c f2 e9 14 16 3d + c6 73 92 4d 64 21 e3 0b 9e cf 9a 15 4f 59 6f 08 + 83 cc 5d c8 f8 91 7b 25 7f ea 4e e6 03 3c 5b a4 + 66 88 01 85 d8 37 b2 fc 64 bd c8 25 5a 79 92 b1 + 99 4b a3 93 65 26 7b 33 bf e6 69 b6 39 7c a9 4b + 40 2e e1 3b 28 a6 79 82 69 41 98 f6 14 ae 60 9b + d7 4c a2 9a 3d 8c 37 f9 6c 03 3f 32 b6 68 2c 64 + 5c cb f3 9a 49 f3 33 e3 0c a6 dd da 29 2f 98 76 + 80 d4 73 df 64 f9 cb b3 58 33 60 36 d3 13 d6 9b + 9c b6 9a 3b 98 5f b2 07 2e 64 dc c9 7c 91 4b 24 + f8 08 cb 6e 45 8d 47 32 1d 12 77 b8 81 4a 59 17 + 68 6a 1f 60 df 28 ac a9 3d 85 b5 5b b6 62 0a ff + 0c 69 90 7b 36 b3 6c 65 d3 9c c9 f4 40 b1 93 a5 + 47 e0 32 7f 8a e6 54 d6 93 6c a2 0f 14 17 c8 03 + 00''') + for byte in cblock_data: + write_byte(buf, byte) + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 10 + + for ii, gg in enumerate(geometry): + msg = f'Failed on geometry {ii}' + assert gg.x == [110, 900, 1520, -370, 1690, -50, 180, 1540, 970, 2160][ii], msg + assert gg.y == [1270, 890, 2000, 1260, 1420, 850, 860, 750, 1740, 2000][ii], msg + if ii == 0: + assert gg.layer == 0, msg + else: + assert gg.layer == 1, msg + assert gg.datatype == 0, msg + + assert not gg.properties, msg + assert gg.repetition is None, msg + + assert geometry[0].height == 530 + assert geometry[0].width == 540 + assert geometry[1].height == 610 + assert geometry[1].width == 680 + + assert_equal(geometry[2].point_list, + [[-30, -360], [480, -50], [180, 430], [-630, -20]]) + + assert_equal(geometry[3].point_list, + [[-30, -400], + [450, 40], + [70, -220], + [10, 210], + [740, -20], + [0, 660], + [570, 10], + [50, 500], + [630, 20], + [10, 100], + [-810, 10], + [20, -470], + [-660, 0], + [20, -470], + [-620, 10], + [0, 610], + [610, -10], + [0, -100], + [210, 10], + [40, 820], + [-1340, 60], + [30, -1370]]) + + assert_equal(geometry[4].point_list, + [[40, -760], [490, -50], [110, 800], [-640, 10]]) + + assert_equal(geometry[5].point_list, + [[140, -380], + [340, -10], + [30, -100], + [-320, 20], + [130, -460], + [-480, -20], + [-210, 910], + [370, 40]]) + + assert_equal(geometry[6].point_list, + [[720, -20], + [20, 20], + [690, 0], + [-10, 650], + [-20, 30], + [-90, -10], + [10, 70], + [470, -30], + [20, -120], + [-320, 0], + [40, -790], + [-90, -20], + [-60, 140], + [-1390, 50], + [10, 30]]) + + assert_equal(geometry[7].point_list, + [[150, -830], + [-1320, 40], + [-70, 370], + [310, -30], + [10, 220], + [250, -40], + [40, -220], + [340, 10], + [-20, 290], + [-1070, 20], + [0, 230], + [1380, -60]]) + + assert_equal(geometry[8].point_list, + [[330, 0], [-10, 480], [620, -20], [-10, 330], [-930, 60], [0, -850]]) + + assert_equal(geometry[9].point_list, + [[-140, -410], + [10, -140], + [270, 0], + [130, 1030], + [-500, 50], + [10, -330], + [210, -10], + [10, -190]]) diff --git a/fatamorgana/test/test_files_cells.py b/fatamorgana/test/test_files_cells.py new file mode 100644 index 0000000..9239994 --- /dev/null +++ b/fatamorgana/test/test_files_cells.py @@ -0,0 +1,275 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.propstrings + assert not layout.layers + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Single cell with explicit name 'XYZ' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'XYZ') # Cell name + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'XYZ' + assert not layout.cellnames + + +def write_file_2(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Two cellnames ('XYZ', 'ABC') and two cells with name references. + ''' + buf.write(HEADER) + + write_uint(buf, 3) # CELLNAME record (implicit id 0) + write_bstring(buf, b'XYZ') + + write_uint(buf, 3) # CELLNAME record (implicit id 1) + write_bstring(buf, b'ABC') + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 (ABC) + + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_2(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cellnames) == 2 + assert len(layout.cells) == 2 + assert layout.cellnames[0].nstring.string == 'XYZ' + assert layout.cellnames[1].nstring.string == 'ABC' + assert layout.cells[0].name == 0 + assert layout.cells[1].name == 1 + + +def write_file_3(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Invalid file, contains a mix of explicit and implicit cellnames + ''' + buf.write(HEADER) + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'ABC') + write_uint(buf, 1) # id 1 + + write_uint(buf, 3) # CELLNAME record (implicit id 0) -- Expect failure due to mix of explicit/implicit ids + write_bstring(buf, b'XYZ') + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 (ABC) + + buf.write(FOOTER) + return buf + + +def test_file_3() -> None: + buf = write_file_3(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidRecordError): + layout = OasisLayout.read(buf) + + +def write_file_4(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Two cells referencing two names with explicit ids (unsorted) + ''' + buf.write(HEADER) + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'ABC') + write_uint(buf, 1) # id 1 + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'XYZ') + write_uint(buf, 0) # id 0 + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 (ABC) + + buf.write(FOOTER) + return buf + + +def test_file_4() -> None: + buf = write_file_4(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cellnames) == 2 + assert len(layout.cells) == 2 + assert layout.cellnames[0].nstring.string == 'XYZ' + assert layout.cellnames[1].nstring.string == 'ABC' + assert layout.cells[0].name == 0 + assert layout.cells[1].name == 1 + + +def write_file_5(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Reference to non-existent cell name. + ''' + buf.write(HEADER) + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'ABC') + write_uint(buf, 1) # id 1 + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'XYZ') + write_uint(buf, 0) # id 0 + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 2) # Cell name 2 -- Reference to non-existent CELLNAME!!! + + buf.write(FOOTER) + return buf + + +def test_file_5() -> None: + buf = write_file_5(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cellnames) == 2 + assert len(layout.cells) == 2 + assert layout.cellnames[0].nstring.string == 'XYZ' + assert layout.cellnames[1].nstring.string == 'ABC' + assert layout.cells[0].name == 0 + assert layout.cells[1].name == 2 + + #TODO add optional error checking for this case + + +def write_file_6(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Cellname with invalid n-string. + ''' + buf.write(HEADER) + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'ABC') + write_uint(buf, 1) # id 1 + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b' XYZ') + write_uint(buf, 0) # id 0 + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 + + buf.write(FOOTER) + return buf + + +def test_file_6() -> None: + buf = write_file_6(BytesIO()) + + buf.seek(0) + + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) + + #base_tests(layout) + #assert len(layout.cellnames) == 2 + #assert len(layout.cells) == 2 + #assert layout.cellnames[0].nstring.string == ' XYZ' + #assert layout.cellnames[1].nstring.string == 'ABC' + #assert layout.cells[0].name == 0 + #assert layout.cells[1].name == 1 + + +def write_file_7(buf: BufferedIOBase) -> BufferedIOBase: + ''' + Unused cellname. + ''' + buf.write(HEADER) + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'ABC') + write_uint(buf, 1) # id 1 + + write_uint(buf, 4) # CELLNAME record (explicit id) + write_bstring(buf, b'XYZ') + write_uint(buf, 0) # id 0 + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + buf.write(FOOTER) + return buf + + +def test_file_7() -> None: + buf = write_file_7(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cellnames) == 2 + assert len(layout.cells) == 1 + assert layout.cellnames[0].nstring.string == 'XYZ' + assert layout.cellnames[1].nstring.string == 'ABC' + assert layout.cells[0].name == 0 diff --git a/fatamorgana/test/test_files_circles.py b/fatamorgana/test/test_files_circles.py new file mode 100644 index 0000000..35c7a14 --- /dev/null +++ b/fatamorgana/test/test_files_circles.py @@ -0,0 +1,117 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0011_1011) # 00rX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 150) # radius + write_sint(buf, -100) # geometry-x (absolute) + write_sint(buf, 200) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0000_1000) # 00rX_YRDL + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0010_1000) # 00rX_YRDL + write_uint(buf, 0) # radius + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0010_1000) # 00rX_YRDL + write_uint(buf, 1) # radius + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0010_1000) # 00rX_YRDL + write_uint(buf, 6) # radius + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0010_1000) # 00rX_YRDL + write_uint(buf, 20) # radius + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0010_1100) # 00rX_YRDL + write_uint(buf, 100) # radius + write_sint(buf, 400) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 400) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 7 + for ii, gg in enumerate(geometry): + msg = f'Failed on circle {ii}' + assert gg.x == -100, msg + assert gg.y == 200 + 400 * ii, msg + + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + assert not gg.properties, msg + assert gg.radius == [150, 150, 0, 1, 6, 20, 100][ii], msg + + if ii != 6: + assert gg.repetition is None, msg + + assert geometry[6].repetition.a_count == 3, msg + assert geometry[6].repetition.b_count == 4, msg + assert geometry[6].repetition.a_vector == [400, 0], msg + assert geometry[6].repetition.b_vector == [0, 300], msg diff --git a/fatamorgana/test/test_files_ctrapezoids.py b/fatamorgana/test/test_files_ctrapezoids.py new file mode 100644 index 0000000..b98794a --- /dev/null +++ b/fatamorgana/test/test_files_ctrapezoids.py @@ -0,0 +1,247 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b1111_1011) # TWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 24) # ctrapezoid type + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, -100) # geometry-x (absolute) + write_sint(buf, 200) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b0000_1000) # TWHX_YRDL + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_0011) # SWHX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + + h = [250, 100] + v = [100, 250] + + wh = [h] * 8 + [v] * 8 + [h] * 6 + [v] * 2 + [h] * 2 + + wh_en = ([0b11] * 16 + + [0b10] * 4 + + [0b01] * 2 + + [0b10] * 2 + + [0b11, 0b10]) + + for t, (x, x_en) in enumerate(zip(wh, wh_en)): + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b1000_1011 | (x_en << 5)) # TWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, t) # ctrapezoid type + if x_en & 0b10: + write_uint(buf, x[0]) # width + if x_en & 0b01: + write_uint(buf, x[1]) # height + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_0011) # SWHX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b0000_1100) # TWHX_YRDL + write_sint(buf, 400) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 400) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + + geometry = layout.cells[0].geometry + assert len(geometry) == 3 + 26 * 2 + 1 + + for ii, gg in enumerate(geometry): + msg = f'Failed on shape {ii}' + assert gg.x == -100, msg + assert gg.y == 200 + 400 * ((ii + 1) // 2), msg + + if ii < 2 or (3 <= ii < 55 and ii % 2 == 1): + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + else: + assert gg.layer == 2, msg + assert gg.datatype == 3, msg + + if ii < 3: + assert gg.width == 100, msg + assert gg.height == 200, msg + + assert not gg.properties, msg + + if 3 <= ii < 55: + ct_type = (ii - 3) // 2 + is_ctrapz = ii % 2 == 1 + if is_ctrapz: + assert gg.ctrapezoid_type == ct_type, msg + if ct_type in range(16, 20): + assert gg.height == [250, None][is_ctrapz], msg + elif ct_type in (20, 21): + assert gg.width == [250, None][is_ctrapz], msg + elif ct_type in range(22, 24) or ct_type == 25: + assert gg.height == [100, None][is_ctrapz], msg + else: + if ct_type < 8 or 16 <= ct_type < 25 or 26 <= ct_type : + assert gg.width == 250, msg + assert gg.height == 100, msg + else: + assert gg.width == 100, msg + assert gg.height == 250, msg + elif ii < 3 and ii % 2: + assert gg.ctrapezoid_type == 24, msg + elif ii == 55: + assert gg.ctrapezoid_type == 25, msg + + assert geometry[55].repetition.a_count == 3 + assert geometry[55].repetition.b_count == 4 + assert geometry[55].repetition.a_vector == [400, 0] + assert geometry[55].repetition.b_vector == [0, 300] + + +def write_file_2(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + # Shouldn't access (undefined) height modal, despite not having a height. + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b1101_1011) # TWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 16) # ctrapezoid type + write_uint(buf, 200) # width + write_sint(buf, -100) # geometry-x (absolute) + write_sint(buf, 200) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b0000_1000) # TWHX_YRDL + write_sint(buf, 400) # geometry-y (relative) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'B') # Cell name + + # Shouldn't access (undefined) width modal, despite not having a width. + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b1011_1011) # TWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 20) # ctrapezoid type + write_uint(buf, 200) # height + write_sint(buf, -100) # geometry-x (absolute) + write_sint(buf, 200) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + write_uint(buf, 26) # CTRAPEZOID record + write_byte(buf, 0b0000_1000) # TWHX_YRDL + write_sint(buf, 400) # geometry-y (relative) + + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_2(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 2 + assert layout.cells[0].name.string == 'A' + assert layout.cells[1].name.string == 'B' + assert not layout.cells[0].properties + assert not layout.cells[1].properties + + for ii, cc in enumerate(layout.cells): + for jj, gg in enumerate(cc.geometry): + msg = f'Fail in cell {ii}, ctrapezoid {jj}' + assert not gg.properties, msg + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + assert gg.x == -100, msg + + geometry = layout.cells[0].geometry + assert geometry[0].width == 200 + assert geometry[1].width == 200 + assert geometry[0].ctrapezoid_type == 16 + assert geometry[1].ctrapezoid_type == 16 + assert geometry[0].y == 200 + assert geometry[1].y == 600 + + geometry = layout.cells[1].geometry + assert geometry[0].height == 200 + assert geometry[1].height == 200 + assert geometry[0].ctrapezoid_type == 20 + assert geometry[1].ctrapezoid_type == 20 + assert geometry[0].y == 200 + assert geometry[1].y == 600 + diff --git a/fatamorgana/test/test_files_empty.py b/fatamorgana/test/test_files_empty.py new file mode 100644 index 0000000..1fbb63c --- /dev/null +++ b/fatamorgana/test/test_files_empty.py @@ -0,0 +1,185 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore + +from .utils import MAGIC_BYTES, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.cells + assert not layout.cellnames + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.propstrings + assert not layout.layers + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File contains one PAD record. + 1000 units/micron + Offset table inside START. + ''' + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 0) # dbu real type: uint + write_uint(buf, 1000) # dbu value: 1000 per micron + write_uint(buf, 0) # offset table is present here + for _ in range(6): + write_uint(buf, 0) # offset table (0: not strict) + write_uint(buf, 0) # offset table (0: no entry present) + + write_uint(buf, 0) # PAD record + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert layout.unit == 1000 + + + +def write_file_2(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File contains no records. + 1/2 unit/micron + Offset table inside START. + ''' + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 2) # dbu real type: fraction 1/x + write_uint(buf, 2) # dbu value: 1/2 per micron + write_uint(buf, 0) # offset table is present here + for _ in range(6): + write_uint(buf, 0) # offset table (0: not strict) + write_uint(buf, 0) # offset table (0: no entry present) + + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_2(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert layout.unit == 0.5 + + +def write_file_3(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File contains no records. + 10/4 unit/micron + Offset table inside START. + ''' + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 4) # dbu real type: fraction a/b + write_uint(buf, 10) # dbu value a + write_uint(buf, 4) # dbu value b: 10/4 per micron + write_uint(buf, 0) # offset table is present here + for _ in range(6): + write_uint(buf, 0) # offset table (0: not strict) + write_uint(buf, 0) # offset table (0: no entry present) + + buf.write(FOOTER) + return buf + + +def test_file_3() -> None: + buf = write_file_3(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert layout.unit == 10 / 4 + + +def write_file_4(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File contains no records. + 12.5 unit/micron (float32) + Offset table inside START. + ''' + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 6) # dbu real type: float32 + buf.write(struct.pack(" None: + buf = write_file_4(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert layout.unit == 12.5 + + +def write_file_5(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File contains no records. + 12.5 unit/micron (float64) + Offset table inside START. + ''' + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 7) # dbu real type: float64 + buf.write(struct.pack(" None: + buf = write_file_5(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert layout.unit == 12.5 diff --git a/fatamorgana/test/test_files_layernames.py b/fatamorgana/test/test_files_layernames.py new file mode 100644 index 0000000..56ddc23 --- /dev/null +++ b/fatamorgana/test/test_files_layernames.py @@ -0,0 +1,338 @@ +# type: ignore + +from typing import List, Tuple, Iterable, Sequence +from itertools import chain +from io import BytesIO, BufferedIOBase + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +LAYERS = [(1, 2), (1, 5), (1, 6), (1, 8), + (5, 2), (5, 5), (5, 6), (5, 8), + (6, 2), (6, 5), (6, 6), (6, 8), + (7, 2), (7, 5), (7, 6), (7, 8), + ] + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + + +def write_names_geom(buf: BufferedIOBase, short: bool = False) -> BufferedIOBase: + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'AA') # name + write_uint(buf, 0) # all layers + write_uint(buf, 0) # all datatypes + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'L5A') # name + write_uint(buf, 1) # layer <=5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'H5A') # name + write_uint(buf, 2) # layer >=5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'E5A') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'I56A') # name + write_uint(buf, 4) # layer 5 to 6 + write_uint(buf, 5) # (...) + write_uint(buf, 6) # (...) + write_uint(buf, 0) # all datatypes + + if short: + return buf + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'E5L4') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 1) # datatype <=4 + write_uint(buf, 4) # (...) + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'E5H4') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 2) # datatype >=4 + write_uint(buf, 4) # (...) + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'E5E4') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 3) # datatype ==4 + write_uint(buf, 4) # (...) + + write_uint(buf, 11) # LAYERNAME record (geometry) + write_bstring(buf, b'E5I47') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 4) # datatype 4 to 7 + write_uint(buf, 4) # (...) + write_uint(buf, 7) # (...) + + return buf + + +def write_names_text(buf: BufferedIOBase, prefix: bytes = b'') -> BufferedIOBase: + write_uint(buf, 12) # LAYERNAME record (geometry) + write_bstring(buf, prefix + b'AA') # name + write_uint(buf, 0) # all layers + write_uint(buf, 0) # all datatypes + + write_uint(buf, 12) # LAYERNAME record (geometry) + write_bstring(buf, prefix + b'L5A') # name + write_uint(buf, 1) # layer <=5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 12) # LAYERNAME record (geometry) + write_bstring(buf, prefix + b'H5A') # name + write_uint(buf, 2) # layer >=5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 12) # LAYERNAME record (geometry) + write_bstring(buf, prefix + b'E5A') # name + write_uint(buf, 3) # layer ==5 + write_uint(buf, 5) # (...) + write_uint(buf, 0) # all datatypes + + write_uint(buf, 12) # LAYERNAME record (geometry) + write_bstring(buf, prefix + b'I56A') # name + write_uint(buf, 4) # layer 5 to 6 + write_uint(buf, 5) # (...) + write_uint(buf, 6) # (...) + write_uint(buf, 0) # all datatypes + return buf + +def write_geom(buf: BufferedIOBase) -> BufferedIOBase: + for ll, dt in LAYERS: + write_uint(buf, 27) # CIRCLE record + write_byte(buf, 0b0011_1011) # 00rX_YRDL + write_uint(buf, ll) # layer + write_uint(buf, dt) # datatype + write_uint(buf, 150) # radius + write_sint(buf, ll * 1000) # geometry-x (absolute) + write_sint(buf, dt * 1000) # geometry-y (absolute) + return buf + + +def write_text(buf: BufferedIOBase) -> BufferedIOBase: + for ll, dt in LAYERS: + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0101_1011) # 0CNX_YRTL + write_bstring(buf, b'A') # text-string + write_uint(buf, ll) # text-layer + write_uint(buf, dt) # text-datatype + write_sint(buf, ll * 1000) # geometry-x + write_sint(buf, dt * 1000) # geometry-y + return buf + + +def name_test(layers: Sequence, is_textlayer: bool) -> None: + for ii, nn in enumerate(layers): + assert is_textlayer == nn.is_textlayer, f'Fail on layername {ii}' + + assert nn.nstring.string == ['AA', 'L5A', 'H5A', 'E5A', 'I56A', + 'E5L4', 'E5H4', 'E5E4', 'E5I47'][ii], msg + assert nn.layer_interval[0] == [None, None, 5, 5, 5, 5, 5, 5, 5][ii], msg + assert nn.layer_interval[1] == [None, 5, None, 5, 6, 5, 5, 5, 5][ii], msg + assert nn.type_interval[0] == [None, None, None, None, None, None, 4, 4, 4][ii], msg + assert nn.type_interval[1] == [None, None, None, None, None, 4, None, 4, 7][ii], msg + + +def name_test_text(layers: Sequence) -> None: + for ii, nn in enumerate(layers): + assert nn.is_textlayer, f'Fail on layername {ii}' + + assert nn.nstring.string == ['TAA', 'TL5A', 'TH5A', 'TE5A', 'TI56A'][ii], msg + assert nn.layer_interval[0] == [None, None, 5, 5, 5][ii], msg + assert nn.layer_interval[1] == [None, 5, None, 5, 6][ii], msg + assert nn.type_interval[0] == [None, None, None, None, None][ii], msg + assert nn.type_interval[1] == [None, None, None, None, None][ii], msg + + +def elem_test_geom(geometry: Sequence) -> None: + for ii, gg in enumerate(geometry): + msg = f'Failed on circle ({ii})' + assert gg.x == 1000 * LAYERS[ii][0], msg + assert gg.y == 1000 * LAYERS[ii][1], msg + assert gg.radius == 150, msg + + assert gg.layer == LAYERS[ii][0], msg + assert gg.datatype == LAYERS[ii][1], msg + + assert gg.repetition is None, msg + assert not gg.properties, msg + + +def elem_test_text(geometry: Sequence) -> None: + for ii, gg in enumerate(geometry): + msg = f'Failed on text ({ii})' + assert gg.x == 1000 * LAYERS[ii][0], msg + assert gg.y == 1000 * LAYERS[ii][1], msg + assert gg.string.string == 'A', msg + + assert gg.layer == LAYERS[ii][0], msg + assert gg.datatype == LAYERS[ii][1], msg + + assert gg.repetition is None, msg + assert not gg.properties, msg + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + write_names_geom(buf) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_geom(buf) + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == len(LAYERS) + elem_test_geom(geometry) + + assert len(layout.layers) == 9 + name_test(layout.layers, is_textlayer=False) + + +def write_file_2(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + write_names_text(buf) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_text(buf) + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_2(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == len(LAYERS) + elem_test_text(geometry) + + assert len(layout.layers) == 5 + name_test(layout.layers, is_textlayer=True) + + +def write_file_3(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + write_names_text(buf, prefix=b'T') + write_names_geom(buf, short=True) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_text(buf) + write_geom(buf) + buf.write(FOOTER) + return buf + + +def write_file_4(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_text(buf) + write_geom(buf) + + write_names_text(buf, prefix=b'T') + write_names_geom(buf, short=True) + buf.write(FOOTER) + return buf + + +def test_file_3() -> None: + buf = write_file_3(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 2 * len(LAYERS) + elem_test_text(geometry[:len(LAYERS)]) + elem_test_geom(geometry[len(LAYERS):]) + + assert len(layout.layers) == 2 * 5 + name_test_text(layout.layers[:5]) + name_test(layout.layers[5:], is_textlayer=False) + + +def test_file_4() -> None: + buf = write_file_4(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 2 * len(LAYERS) + elem_test_text(geometry[:len(LAYERS)]) + elem_test_geom(geometry[len(LAYERS):]) + + assert len(layout.layers) == 2 * 5 + name_test_text(layout.layers[:5]) + name_test(layout.layers[5:], is_textlayer=False) diff --git a/fatamorgana/test/test_files_modals.py b/fatamorgana/test/test_files_modals.py new file mode 100644 index 0000000..0021bf1 --- /dev/null +++ b/fatamorgana/test/test_files_modals.py @@ -0,0 +1,260 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0110_0011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 10) # width + write_uint(buf, 20) # height + + # TEXT 1 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0100_0011) # 0CNX_YRTL + write_bstring(buf, b'A') # text string + write_uint(buf, 2) # layer + write_uint(buf, 1) # datatype + + # RECTANGLE 2 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 100) # geometry-x (absolute) + write_sint(buf, -100) # geometry-y (absolute) + + # TEXT 3 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 100) # text-x (absolute) + write_sint(buf, -100) # text-y (absolute) + + # RECTANGLE 4 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 200) # geometry-x (absolute) + write_sint(buf, -200) # geometry-y (absolute) + + # TEXT 5 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 200) # text-x (absolute) + write_sint(buf, -200) # text-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # RECTANGLE 6 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 100) # geometry-x (relative) + write_sint(buf, -100) # geometry-y (relative) + + # TEXT 7 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 100) # text-x (relative) + write_sint(buf, -100) # text-y (relative) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'B') # Cell name + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0110_0011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 20) # width + write_uint(buf, 10) # height + + # TEXT 1 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0100_0011) # 0CNX_YRTL + write_bstring(buf, b'B') # text string + write_uint(buf, 2) # layer + write_uint(buf, 1) # datatype + + # RECTANGLE 2 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 100) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + # TEXT 3 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 100) # text-x (absolute) + write_sint(buf, 100) # text-y (absolute) + + # RECTANGLE 4 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 200) # geometry-x (absolute) + write_sint(buf, 200) # geometry-y (absolute) + + # TEXT 5 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 200) # text-x (absolute) + write_sint(buf, 200) # text-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # RECTANGLE 6 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0001_1000) # SWHX_YRDL + write_sint(buf, 100) # geometry-x (relative) + write_sint(buf, 100) # geometry-y (relative) + + # TEXT 7 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1000) # 0CNX_YRTL + write_sint(buf, 100) # text-x (relative) + write_sint(buf, 100) # text-y (relative) + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b1000_0000) # CNXY_RAAF + write_bstring(buf, b'A') # Cell reference + + # PLACEMENT 1 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_0000) # CNXY_RAAF + write_sint(buf, 50) # placement-x (relative) + write_sint(buf, 50) # placement-y (relative) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b1000_0000) # CNXY_RAAF + write_bstring(buf, b'B') # Cell reference + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0110_0011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 50) # width + write_uint(buf, 5) # height + + # TEXT 1 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0100_0011) # 0CNX_YRTL + write_bstring(buf, b'TOP') # text string + write_uint(buf, 2) # layer + write_uint(buf, 1) # datatype + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 3 + assert layout.cells[0].name.string == 'A' + assert layout.cells[1].name.string == 'B' + assert layout.cells[2].name.string == 'TOP' + assert not layout.cells[0].properties + assert not layout.cells[1].properties + assert not layout.cells[2].properties + + geometry = layout.cells[0].geometry + assert len(geometry) == 8 + for ii, gg in enumerate(geometry): + msg = f'Failed on geometry {ii} in cell A' + + if ii % 2 == 0: + assert gg.width == 10, msg + assert gg.height == 20, msg + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + else: + assert gg.string.string == 'A', msg + assert gg.layer == 2, msg + assert gg.datatype == 1, msg + assert not gg.properties, msg + assert gg.x == (ii // 2) * 100, msg + assert gg.y == (ii // 2) * -100, msg + + geometry = layout.cells[1].geometry + assert len(geometry) == 8 + for ii, gg in enumerate(geometry): + msg = f'Failed on geometry {ii} in cell B' + + if ii % 2 == 0: + assert gg.width == 20, msg + assert gg.height == 10, msg + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + else: + assert gg.string.string == 'B', msg + assert gg.layer == 2, msg + assert gg.datatype == 1, msg + assert not gg.properties, msg + assert gg.x == (ii // 2) * 100, msg + assert gg.y == (ii // 2) * 100, msg + + assert layout.cells[1].placements[0].name.string == 'A' + assert layout.cells[1].placements[1].name.string == 'A' + assert layout.cells[1].placements[0].x == 0 + assert layout.cells[1].placements[0].y == 0 + assert layout.cells[1].placements[1].x == 50 + assert layout.cells[1].placements[1].y == 50 + + assert layout.cells[2].placements[0].name.string == 'B' + assert layout.cells[2].placements[0].x == 0 + assert layout.cells[2].placements[0].y == 0 + + assert layout.cells[2].geometry[0].layer == 1 + assert layout.cells[2].geometry[0].datatype == 2 + assert layout.cells[2].geometry[0].width == 50 + assert layout.cells[2].geometry[0].height == 5 + assert layout.cells[2].geometry[0].x == 0 + assert layout.cells[2].geometry[0].y == 0 + + assert layout.cells[2].geometry[1].layer == 2 + assert layout.cells[2].geometry[1].datatype == 1 + assert layout.cells[2].geometry[1].string.string == 'TOP' + assert layout.cells[2].geometry[1].x == 0 + assert layout.cells[2].geometry[1].y == 0 diff --git a/fatamorgana/test/test_files_paths.py b/fatamorgana/test/test_files_paths.py new file mode 100644 index 0000000..9bdb6f8 --- /dev/null +++ b/fatamorgana/test/test_files_paths.py @@ -0,0 +1,205 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABC' + assert not layout.cells[0].properties + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + # PATH 0 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1111_1011) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 10) # half-width + write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE + write_sint(buf, 5) # (extension-scheme) start + write_sint(buf, -5) # (extension-scheme) end + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 0) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # PATH 1 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1110_1011) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 10) # half-width + write_byte(buf, 0b0000_0000) # extension-scheme 0000_SSEE + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 200) # geometry-y (relative) + + # PATH 2 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1110_1001) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 10) # half-width + write_byte(buf, 0b0000_0100) # extension-scheme 0000_SSEE + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 200) # geometry-y (relative) + + # PATH 3 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1110_1010) # EWPX_YRDL + write_uint(buf, 2) # datatype + write_uint(buf, 12) # half-width + write_byte(buf, 0b0000_0101) # extension-scheme 0000_SSEE + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 200) # geometry-y (relative) + + # PATH 4 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1010_1011) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_byte(buf, 0b0000_1010) # extension-scheme 0000_SSEE + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 200) # geometry-y (relative) + + # PATH 5 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b0000_1011) # EWPX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_sint(buf, 200) # geometry-y (relative) + + # PATH 6 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b0000_1111) # EWPX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_sint(buf, 200) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 16) # XYRELATIVE record + + # PATH 7 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b0001_0101) # EWPX_YRDL + write_uint(buf, 1) # layer #NOTE: fixed + write_sint(buf, 1000) # geometry-x (relative) + write_uint(buf, 0) # repetition (reuse) + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 8 + + for ii, gg in enumerate(geometry): + msg = f'Failed on path {ii}' + if ii < 7: + assert gg.y == 100 + ii * 200, msg + assert gg.x == 0, msg + else: + assert gg.x == 1000, msg + assert gg.y == 1300, msg + + if ii < 5: + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + elif ii < 7: + assert gg.layer == 2, msg + assert gg.datatype == 3, msg + else: + assert gg.layer == 1, msg + assert gg.datatype == 3, msg + + if ii < 6: + assert gg.repetition is None, msg + elif ii in (7, 8): + assert gg.repetition.a_count == 3, msg + assert gg.repetition.b_count == 4, msg + assert gg.repetition.a_vector == [200, 0], msg + assert gg.repetition.b_vector == [0, 300], msg + assert not gg.properties, msg + + if ii < 3: + assert gg.half_width == 10, msg + else: + assert gg.half_width == 12, msg + + assert len(gg.point_list) == 3, msg + assert_equal(gg.point_list, [[150, 0], [0, 50], [-50, 0]], err_msg=msg) + + if ii >= 4: + assert gg.extension_start == (PathExtensionScheme.HalfWidth, None) + assert gg.extension_end == (PathExtensionScheme.HalfWidth, None) + + assert geometry[0].extension_start == (PathExtensionScheme.Arbitrary, 5) + assert geometry[1].extension_start == (PathExtensionScheme.Arbitrary, 5) + assert geometry[2].extension_start == (PathExtensionScheme.Flush, None) + assert geometry[3].extension_start == (PathExtensionScheme.Flush, None) + assert geometry[0].extension_end == (PathExtensionScheme.Arbitrary, -5) + assert geometry[1].extension_end == (PathExtensionScheme.Arbitrary, -5) + assert geometry[2].extension_end == (PathExtensionScheme.Arbitrary, -5) + assert geometry[3].extension_end == (PathExtensionScheme.Flush, None) diff --git a/fatamorgana/test/test_files_placements.py b/fatamorgana/test/test_files_placements.py new file mode 100644 index 0000000..5059be9 --- /dev/null +++ b/fatamorgana/test/test_files_placements.py @@ -0,0 +1,895 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError, write_float32, write_float64 +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.layers + + +def write_rectangle(buf: BufferedIOBase, pos: Tuple[int, int] = (300, -400)) -> None: + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, pos[0]) # geometry-x (absolute) + write_sint(buf, pos[1]) # geometry-y (absolute) + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_rectangle(buf) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b1011_0000) # CNXY_RAAF + write_bstring(buf, b'A') # cell reference + write_sint(buf, -300) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 1 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_0000) # CNXY_RAAF + write_sint(buf, 0) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 2 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0000) # CNXY_RAAF + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 3 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0010_0000) # CNXY_RAAF + write_sint(buf, 300) # placement-x (relative) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 4 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_0001) # CNXY_RAAF + write_sint(buf, 700) # placement-x (absolute) + write_sint(buf, 400) # placement-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 5 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0010) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + # PLACEMENT 6 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0011) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 7 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (absolute) + write_sint(buf, 0) # placement-y (absolute) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 300) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 8 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + write_uint(buf, 0) # repetition (reuse) + + # PLACEMENT 9 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + write_uint(buf, 2) # repetition (3 cols.) + write_uint(buf, 1) # (repetition) count + write_uint(buf, 320) # (repetition) spacing + + # PLACEMENT 10 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + write_uint(buf, 3) # repetition (4 rows) + write_uint(buf, 2) # (repetition) count + write_uint(buf, 310) # (repetition) spacing + + # PLACEMENT 11 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + write_uint(buf, 4) # repetition (4 arbitrary cols.) + write_uint(buf, 2) # (repetition) dimension + write_uint(buf, 320) # (repetition) spacing + write_uint(buf, 330) # (repetition) spacing + write_uint(buf, 340) # (repetition) spacing + + # PLACEMENT 12 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + write_uint(buf, 8) # repetition (3x4 matrix, arbitrary vectors) + write_uint(buf, 1) # (repetition) n-dimension + write_uint(buf, 2) # (repetition) m-dimension + write_uint(buf, 310 << 2 | 0b01) # (repetition) n-displacement g-delta: (310, 320) + write_sint(buf, 320) # (repetition g-delta) + write_uint(buf, 330 << 4 | 0b1010) # (repetition) m-displacement g-delta: 330-northwest (-330, 330) + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 2 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + assert not layout.cells[0].placements + assert layout.cells[1].name.string == 'TOP' + assert not layout.cells[1].properties + assert not layout.cells[1].geometry + + geometry = layout.cells[0].geometry + assert len(geometry) == 1 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + assert geometry[0].x == 300 + assert geometry[0].y == -400 + + placements = layout.cells[1].placements + assert len(placements) == 13 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii}' + + assert not pp.properties, msg + assert pp.name.string == 'A', msg + + if ii < 3: + assert pp.x == -300, msg + elif ii == 3: + assert pp.x == 0, msg + elif 4 <= ii < 7: + assert pp.x == 700, msg + else: + assert pp.x == 2000 * (ii - 6), msg + + if ii < 3: + assert pp.y == 400 * (ii + 1), msg + elif 7 <= ii: + assert pp.y == 0, msg + + if ii < 4 or ii == 5: + assert pp.flip == False, msg + else: + assert pp.flip == True, msg + + if ii < 5: + assert pp.angle == 0, msg + elif ii in (5, 6): + assert pp.angle == 90, msg + elif 7 <= ii: + assert pp.angle == 270, msg + + if ii < 7: + assert pp.repetition is None, msg + elif ii in (7, 8): + assert pp.repetition.a_count == 3, msg + assert pp.repetition.b_count == 4, msg + assert pp.repetition.a_vector == [300, 0], msg + assert pp.repetition.b_vector == [0, 300], msg + + assert placements[3].y == 1200 + assert placements[4].y == 400 + assert placements[5].y == 1400 + assert placements[6].y == 2400 + + assert placements[9].repetition.a_count == 3 + assert placements[9].repetition.b_count is None + assert placements[9].repetition.a_vector == [320, 0] + assert placements[9].repetition.b_vector is None + + assert placements[10].repetition.a_count == 4 + assert placements[10].repetition.b_count is None + assert placements[10].repetition.a_vector == [0, 310] + assert placements[10].repetition.b_vector is None + + assert_equal(placements[11].repetition.x_displacements, [320, 330, 340]) + assert_equal(placements[11].repetition.y_displacements, [0, 0, 0]) + + assert placements[12].repetition.a_count == 3 + assert placements[12].repetition.b_count == 4 + assert placements[12].repetition.a_vector == [310, 320] + assert placements[12].repetition.b_vector == [-330, 330] + + +def write_file_common(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + assert variant in (2, 3, 5, 7), 'Error in test definition!' + + buf.write(HEADER) + + if variant in (3, 5): + write_uint(buf, 3) # CELLNAME record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 3) # CELLNAME record (implicit id 1) + write_bstring(buf, b'TOP') + + if variant == 3: + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (A) + + write_rectangle(buf) + + if variant == 2: + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + else: + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 (TOP) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT (simple) + if variant == 2: + write_byte(buf, 0b1011_0000) # CNXY_RAAF + write_bstring(buf, b'A') # cell reference + else: + write_byte(buf, 0b1111_0000) # CNXY_RAAF + write_uint(buf, 0) # cell reference + write_sint(buf, -300) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 1 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_0000) # CNXY_RAAF + write_sint(buf, 0) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 2 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0000) # CNXY_RAAF + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 3 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0010_0000) # CNXY_RAAF + write_sint(buf, 300) # placement-x (relative) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 4 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_0001) # CNXY_RAAF + write_sint(buf, 700) # placement-x (absolute) + write_sint(buf, 400) # placement-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 5 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0010) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + # PLACEMENT 6 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_0011) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + if variant == 2: + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_rectangle(buf) + elif variant in (5, 7): + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (A) + + write_rectangle(buf) + + if variant == 7: + write_uint(buf, 3) # CELLNAME record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 3) # CELLNAME record (implicit id 1) + write_bstring(buf, b'TOP') + + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_common(BytesIO(), 2) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.cells) == 2 + assert layout.cells[0].name.string == 'TOP' + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name.string == 'A' + assert not layout.cells[1].properties + assert not layout.cells[1].placements + assert not layout.cellnames + + common_tests(layout, 2) + + +def test_file_3() -> None: + buf = write_file_common(BytesIO(), 3) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.cells) == 2 + assert layout.cells[0].name == 0 + assert not layout.cells[1].properties + assert not layout.cells[1].geometry + assert layout.cells[1].name == 1 + assert not layout.cells[0].properties + assert not layout.cells[0].placements + + assert len(layout.cellnames) == 2 + assert layout.cellnames[0].nstring.string == 'A' + assert layout.cellnames[1].nstring.string == 'TOP' + + common_tests(layout, 3) + + +def test_file_4() -> None: + buf = write_file_4(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.cells) == 2 + assert layout.cells[0].name == 1 + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name == 0 + assert not layout.cells[1].properties + assert not layout.cells[1].placements + + assert len(layout.cellnames) == 2 + assert layout.cellnames[0].nstring.string == 'A' + assert layout.cellnames[1].nstring.string == 'TOP' + + common_tests(layout, 4) + + for ii, pp in enumerate(layout.cells[0].placements): + msg = f'Fail on placement {ii}' + assert pp.repetition.a_count == 3, msg + assert pp.repetition.b_count == 4, msg + assert pp.repetition.a_vector == [20, 0], msg + assert pp.repetition.b_vector == [0, 30], msg + + +def test_file_5() -> None: + buf = write_file_common(BytesIO(), 5) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.cells) == 2 + assert layout.cells[0].name == 1 + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name == 0 + assert not layout.cells[1].properties + assert not layout.cells[1].placements + + assert len(layout.cellnames) == 2 + assert layout.cellnames[0].nstring.string == 'A' + assert layout.cellnames[1].nstring.string == 'TOP' + + common_tests(layout, 5) + + +def test_file_7() -> None: + buf = write_file_common(BytesIO(), 7) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.cells) == 2 + assert layout.cells[0].name == 1 + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name == 0 + assert not layout.cells[1].properties + assert not layout.cells[1].placements + + assert len(layout.cellnames) == 2 + assert layout.cellnames[0].nstring.string == 'A' + assert layout.cellnames[1].nstring.string == 'TOP' + + common_tests(layout, 7) + + +def common_tests(layout: OasisLayout, variant: int) -> None: + base_tests(layout) + + if variant == 3: + geom_cell = 0 + top_cell = 1 + else: + geom_cell = 1 + top_cell = 0 + + geometry = layout.cells[geom_cell].geometry + assert len(geometry) == 1 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + assert geometry[0].x == 300 + assert geometry[0].y == -400 + + placements = layout.cells[top_cell].placements + assert len(placements) == 7 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii}' + + assert not pp.properties, msg + if variant == 2: + assert pp.name.string == 'A', msg + else: + assert pp.name == 0, msg + + if ii < 3: + assert pp.x == -300, msg + elif ii == 3: + assert pp.x == 0, msg + else: + assert pp.x == 700, msg + + if ii < 3: + assert pp.y == 400 * (ii + 1), msg + + if ii in (4, 6): + assert pp.flip == True, msg + else: + assert pp.flip == False, msg + + if ii in (5, 6): + assert pp.angle == 90, msg + else: + assert pp.angle == 0, msg + + if variant != 4: + assert pp.repetition is None, msg + + assert placements[3].y == 1200 + assert placements[4].y == 400 + assert placements[5].y == 1400 + assert placements[6].y == 2400 + + +def write_file_4(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 3) # CELLNAME record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 3) # CELLNAME record (implicit id 1) + write_bstring(buf, b'TOP') + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 1) # Cell name 1 (TOP) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b1111_1000) # CNXY_RAAF + write_uint(buf, 0) # cell reference + write_sint(buf, -300) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 20) # (repetition) x-spacing + write_uint(buf, 30) # (repetition) y-spacing + + # PLACEMENT 1 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1000) # CNXY_RAAF + write_sint(buf, 0) # placement-x (relative) + write_sint(buf, 400) # placement-y (relative) + write_uint(buf, 0) # repetition (reuse) + + # PLACEMENT 2 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_1000) # CNXY_RAAF + write_sint(buf, 400) # placement-y (relative) + write_uint(buf, 0) # repetition (reuse) + + # PLACEMENT 3 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0010_1000) # CNXY_RAAF + write_sint(buf, 300) # placement-x (relative) + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 4 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0011_1001) # CNXY_RAAF + write_sint(buf, 700) # placement-x (absolute) + write_sint(buf, 400) # placement-y (absolute) + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 5 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_1010) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + write_uint(buf, 0) # repetition (reuse) + + # PLACEMENT 6 + write_uint(buf, 17) # PLACEMENT (simple) + write_byte(buf, 0b0001_1011) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (A) + + write_rectangle(buf) + + buf.write(FOOTER) + return buf + + +def write_file_6(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOPTOP') # Cell name + + write_uint(buf, 16) # XYRELATIVE record + + write_uint(buf, 18) # PLACEMENT (mag 0.5, manhattan) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'TOP') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 0.5) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 90.0) # (angle) + write_sint(buf, 100) # placement-x (relative) + write_sint(buf, 0) # placement-y (relative) + + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0011_0000) # CNXY_RMAF + write_sint(buf, 100) # placement-x (relative) + write_sint(buf, 1000) # placement-y (relative) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 0 + write_uint(buf, 18) # PLACEMENT (mag 0.5, manhattan) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'A') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 0.5) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 0.0) # (angle) + write_sint(buf, -150) # placement-x (relative) + write_sint(buf, 200) # placement-y (relative) + + # PLACEMENT 1 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0011_0000) # CNXY_RMAF + write_sint(buf, -150) # placement-x (relative) + write_sint(buf, 600) # placement-y (relative) + + # PLACEMENT 2 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0001_0000) # CNXY_RMAF + write_sint(buf, 400) # placement-y (relative) + + # PLACEMENT 3 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0010_0000) # CNXY_RMAF + write_sint(buf, 300) # placement-x (relative) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 4 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0011_0001) # CNXY_RMAF + write_sint(buf, 700) # placement-x (absolute) + write_sint(buf, 400) # placement-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 5 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0001_0010) # CNXY_RMAF + write_uint(buf, 0) # angle (uint, positive) + write_uint(buf, 90) # (angle) + write_sint(buf, 1000) # placement-y (relative) + + # PLACEMENT 6 + write_uint(buf, 18) # PLACEMENT (no mag, manhattan) + write_byte(buf, 0b0001_0011) # CNXY_RMAF + write_uint(buf, 1) # angle (uint, negative) + write_uint(buf, 90) # (angle) + write_sint(buf, 1000) # placement-y (relative) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_rectangle(buf) + + buf.write(FOOTER) + return buf + + +def test_file_6() -> None: + buf = write_file_6(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 3 + assert layout.cells[0].name.string == 'TOPTOP' + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name.string == 'TOP' + assert not layout.cells[1].properties + assert not layout.cells[1].geometry + assert layout.cells[2].name.string == 'A' + assert not layout.cells[2].properties + assert not layout.cells[2].placements + + geometry = layout.cells[2].geometry + assert len(geometry) == 1 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + assert geometry[0].x == 300 + assert geometry[0].y == -400 + + placements = layout.cells[1].placements + assert len(placements) == 7 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii} in cell TOP' + + assert not pp.properties, msg + assert pp.name.string == 'A', msg + assert pp.flip == (ii in (4, 6)), msg + assert pp.repetition is None, msg + + if ii == 0: + assert pp.magnification == 0.5, msg + else: + assert pp.magnification is None, msg + + assert pp.x == [-150, -300, -300, 0, 700, 700, 700][ii], msg + assert pp.y == [200, 800, 1200, 1200, 400, 1400, 2400][ii], msg + assert pp.angle == [0, None, None, None, None, 90, -90][ii], msg + + placements2 = layout.cells[0].placements + assert len(placements2) == 2 + for ii, pp in enumerate(placements2): + msg = f'Failed on placement {ii} in cell TOPTOP' + + assert not pp.properties, msg + assert pp.name.string == 'TOP', msg + assert not pp.flip, msg + assert pp.repetition is None, msg + + assert pp.angle == [90, None][ii], msg + assert pp.magnification == [0.5, None][ii], msg + assert pp.x == [100, 200][ii], msg + assert pp.y == [0, 1000][ii], msg + + +def write_file_8(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOPTOP') # Cell name + + write_uint(buf, 15) # XYABSOLUTE record + + write_uint(buf, 18) # PLACEMENT (mag 0.5, arbitrary angle) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'TOP') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 0.5) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 22.5) # (angle) + write_sint(buf, 100) # placement-x (absolute) + write_sint(buf, 0) # placement-y (absolute) + + write_uint(buf, 18) # PLACEMENT (mag 1.0, manhattan) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'TOP') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 1.0) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 0.0) # (angle) + write_sint(buf, 1100) # placement-x (absolute) + write_sint(buf, 0) # placement-y (absolute) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + + write_uint(buf, 18) # PLACEMENT (mag 2.0, manhattan) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'A') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 2.0) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 0.0) # (angle) + write_sint(buf, -100) # placement-x (absolute) + write_sint(buf, 100) # placement-y (absolute) + + write_uint(buf, 18) # PLACEMENT (mag 1.0, arbitrary angle) + write_byte(buf, 0b1011_0110) # CNXY_RMAF + write_bstring(buf, b'A') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 1.0) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 45.0) # (angle) + write_sint(buf, -150) # placement-x (absolute) + write_sint(buf, 1100) # placement-y (absolute) + + write_uint(buf, 18) # PLACEMENT (mag 0.5, arbitrary angle) + write_byte(buf, 0b1011_1111) # CNXY_RMAF + write_bstring(buf, b'A') # Cell reference + write_uint(buf, 6) # magnitude, float32 + write_float32(buf, 0.5) # (magnitude) + write_uint(buf, 7) # angle, float64 + write_float64(buf, 135.0) # (angle) + write_sint(buf, -200) # placement-x (absolute) + write_sint(buf, 2100) # placement-y (absolute) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_rectangle(buf, pos=(30, -40)) + + buf.write(FOOTER) + return buf + + +def test_file_8() -> None: + buf = write_file_8(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 3 + assert layout.cells[0].name.string == 'TOPTOP' + assert not layout.cells[0].properties + assert not layout.cells[0].geometry + assert layout.cells[1].name.string == 'TOP' + assert not layout.cells[1].properties + assert not layout.cells[1].geometry + assert layout.cells[2].name.string == 'A' + assert not layout.cells[2].properties + assert not layout.cells[2].placements + + geometry = layout.cells[2].geometry + assert len(geometry) == 1 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + assert geometry[0].x == 30 + assert geometry[0].y == -40 + + placements = layout.cells[1].placements + assert len(placements) == 3 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii} in cell TOP' + + assert not pp.properties, msg + assert pp.name.string == 'A', msg + assert pp.flip == (ii == 2), msg + + assert pp.magnification == [2, 1, 0.5][ii], msg + assert pp.angle == [0, 45, 135][ii], msg + + assert pp.x == [-100, -150, -200][ii], msg + assert pp.y == [100, 1100, 2100][ii], msg + + if ii < 2: + assert pp.repetition is None, msg + assert placements[2].repetition.a_count == 3 + assert placements[2].repetition.b_count == 4 + assert placements[2].repetition.a_vector == [200, 0] + assert placements[2].repetition.b_vector == [0, 300] + + placements2 = layout.cells[0].placements + assert len(placements2) == 2 + for ii, pp in enumerate(placements2): + msg = f'Failed on placement {ii} in cell TOPTOP' + + assert not pp.properties, msg + assert pp.name.string == 'TOP', msg + assert not pp.flip, msg + assert pp.repetition is None, msg + + assert pp.angle == [22.5, 0][ii], msg + assert pp.magnification == [0.5, 1.0][ii], msg + assert pp.x == [100, 1100][ii], msg + assert pp.y == [0, 0][ii], msg diff --git a/fatamorgana/test/test_files_polygons.py b/fatamorgana/test/test_files_polygons.py new file mode 100644 index 0000000..fc5d0c2 --- /dev/null +++ b/fatamorgana/test/test_files_polygons.py @@ -0,0 +1,450 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABC' + assert not layout.cells[0].properties + + +def common_tests(layout: OasisLayout) -> None: + geometry = layout.cells[0].geometry + assert len(geometry) == 12 + + assert geometry[0].x == 0 + assert geometry[0].y == 100 + assert geometry[1].x == -200 + assert geometry[1].y == 400 + assert geometry[2].x == 0 + assert geometry[2].y == 400 + assert geometry[3].x == 0 + assert geometry[3].y == 1000 + assert geometry[4].x == 200 + assert geometry[4].y == 1000 + assert geometry[5].x == 400 + assert geometry[5].y == 1000 + assert geometry[6].x == 700 + assert geometry[6].y == 1000 + assert geometry[7].x == 900 + assert geometry[7].y == 1000 + assert geometry[8].x == 1100 + assert geometry[8].y == 1000 + assert geometry[9].x == 0 + assert geometry[9].y == 2000 + assert geometry[10].x == 1000 + assert geometry[10].y == 2000 + assert geometry[11].x == 2000 + assert geometry[11].y == 2000 + + for ii, gg in enumerate(geometry): + msg = f'Failed on polygon {ii}' + if ii < 2: + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + elif ii < 10: + assert gg.layer == 2, msg + assert gg.datatype == 3, msg + else: + assert gg.layer == 2, msg + assert gg.datatype == 1, msg + + if ii < 9: + assert gg.repetition is None, msg + elif ii in (9, 10): + assert gg.repetition.a_count == 3, msg + assert gg.repetition.b_count == 4, msg + assert gg.repetition.a_vector == [200, 0], msg + assert gg.repetition.b_vector == [0, 300], msg + + assert geometry[11].repetition.y_displacements == [200, 300] + + for ii in range(4): + msg = f'Fail on poly {ii}' + assert len(geometry[0].point_list) == 6, msg + assert_equal(geometry[0].point_list, [[150, 0], [0, 50], [-50, 0], [0, 50], + [-100, 0], [0, -100]], err_msg=msg) + assert len(geometry[4].point_list) == 6 + assert_equal(geometry[4].point_list, [[0, 150], [50, 0], [0, -50], [50, 0], [0, -100], [-100, 0]]) + + assert len(geometry[5].point_list) == 8 + assert_equal(geometry[5].point_list, [[150, 0], [0, 50], [-50, 0], [0, 50], [-50, 0], [0, -50], [-50, 0], [0, -50]]) + assert len(geometry[6].point_list) == 9 + assert_equal(geometry[6].point_list, [[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [0, -50], [50, -50], [25, 0]]) + assert len(geometry[7].point_list) == 9 + assert_equal(geometry[7].point_list, [[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [10, -75], [25, -25], [40, 0]]) + assert len(geometry[8].point_list) == 9 + assert_equal(geometry[8].point_list, + numpy.cumsum([[25, 0], [50, 50], [0, 50], [-50, 50], [-50, 0], [-50, -50], [10, -75], [25, -25], [45, -575]], axis=0)) + + for ii in range(9, 12): + msg = f'Fail on poly {ii}' + assert len(geometry[ii].point_list) == 6, msg + assert_equal(geometry[ii].point_list, [[0, 150], [50, 0], [0, -50], [50, 0], [0, -100], [-100, 0]], err_msg=msg) + + +def write_file_common(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + assert variant in (1, 3), 'Error in test!!' + + buf.write(HEADER) + + if variant == 3: + write_uint(buf, 7) # PROPNAME record (implict id 0) + write_bstring(buf, b'PROP0') # property name + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + # POLYGON 0 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_1011) # 00PX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 0) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + if variant == 3: + # PROPERTY 0 + write_uint(buf, 28) # PROPERTY record (explicit) + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 2) # property value (real: positive reciprocal) + write_uint(buf, 5) # (real) 1/5 + + write_uint(buf, 16) # XYRELATIVE record + + # Polygon 1 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_1011) # 00PX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -200) # geometry-x (relative) + write_sint(buf, 300) # geometry-y (relative) + + if variant == 3: + # PROPERTY 1 + write_uint(buf, 29) # PROPERTY record (repeat) + + write_uint(buf, 15) # XYABSOLUTE record + + # Polygon 2 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 0) # pointlist: 1-delta, horiz-fisrt + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 0) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 2 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 3 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0000_1000) # 00PX_YRDL + write_sint(buf, 1000) # geometry-y (absolute) + + if variant == 3: + # PROPERTY 3 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 4 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 1) # pointlist: 1-delta, vert-fisrt + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 200) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 4 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 5 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 2) # pointlist: 2-delta + write_uint(buf, 7) # (pointlist) dimension + write_uint(buf, 150 << 2 | 0b00) # (pointlist) + write_uint(buf, 50 << 2 | 0b01) # (pointlist) + write_uint(buf, 50 << 2 | 0b10) # (pointlist) + write_uint(buf, 50 << 2 | 0b01) # (pointlist) + write_uint(buf, 50 << 2 | 0b10) # (pointlist) + write_uint(buf, 50 << 2 | 0b11) # (pointlist) + write_uint(buf, 50 << 2 | 0b10) # (pointlist) + write_sint(buf, 400) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 5 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 6 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 3) # pointlist: 3-delta + write_uint(buf, 8) # (pointlist) dimension + write_uint(buf, 25 << 3 | 0b000) # (pointlist) + write_uint(buf, 50 << 3 | 0b100) # (pointlist) + write_uint(buf, 50 << 3 | 0b001) # (pointlist) + write_uint(buf, 50 << 3 | 0b101) # (pointlist) + write_uint(buf, 50 << 3 | 0b010) # (pointlist) + write_uint(buf, 50 << 3 | 0b110) # (pointlist) + write_uint(buf, 50 << 3 | 0b011) # (pointlist) + write_uint(buf, 50 << 3 | 0b111) # (pointlist) + write_sint(buf, 700) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 6 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 7 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 4) # pointlist: g-delta + write_uint(buf, 8) # (pointlist) dimension + write_uint(buf, 25 << 4 | 0b0000) # (pointlist) + write_uint(buf, 50 << 4 | 0b1000) # (pointlist) + write_uint(buf, 50 << 4 | 0b0010) # (pointlist) + write_uint(buf, 50 << 2 | 0b11) # (pointlist) + write_sint(buf, 50) + write_uint(buf, 50 << 4 | 0b0100) # (pointlist) + write_uint(buf, 50 << 4 | 0b1100) # (pointlist) + write_uint(buf, 10 << 2 | 0b01) # (pointlist) + write_sint(buf, -75 ) + write_uint(buf, 25 << 4 | 0b1110) # (pointlist) + write_sint(buf, 900) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 7 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 8 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 5) # pointlist: double g-delta + write_uint(buf, 8) # (pointlist) dimension + write_uint(buf, 25 << 4 | 0b0000) # (pointlist) + write_uint(buf, 50 << 4 | 0b1000) # (pointlist) + write_uint(buf, 50 << 4 | 0b0010) # (pointlist) + write_uint(buf, 50 << 2 | 0b11) # (pointlist) + write_sint(buf, 50) + write_uint(buf, 50 << 4 | 0b0100) # (pointlist) + write_uint(buf, 50 << 4 | 0b1100) # (pointlist) + write_uint(buf, 10 << 2 | 0b01) # (pointlist) + write_sint(buf, -75 ) + write_uint(buf, 25 << 4 | 0b1110) # (pointlist) + write_sint(buf, 1100) # geometry-x (absolute) + + if variant == 3: + # PROPERTY 8 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 9 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_1111) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 1) # pointlist: 1-delta (vert. first) + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 0) # geometry-x (absolute) + write_sint(buf, 2000) # geometry-y (absolute) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + if variant == 3: + # PROPERTY 9 + write_uint(buf, 29) # PROPERTY record (repeat) + + write_uint(buf, 16) # XYRELATIVE record + + # Polygon 10 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0110) # 00PX_YRDL + write_uint(buf, 1) # datatype + write_uint(buf, 1) # pointlist: 1-delta (vert. first) + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 1000) # geometry-x (relative) + write_uint(buf, 0) # repetition (reuse) + + if variant == 3: + # PROPERTY 10 + write_uint(buf, 29) # PROPERTY record (repeat) + + # Polygon 11 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0110) # 00PX_YRDL + write_uint(buf, 1) # datatype + write_uint(buf, 1) # pointlist: 1-delta (vert. first) + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 1000) # geometry-x (relative) + write_uint(buf, 6) # repetition (3 rows) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, 200) # (repetition) y-delta + write_uint(buf, 300) # (repetition) y-delta + + if variant == 3: + # PROPERTY 11 + write_uint(buf, 29) # PROPERTY record (repeat) + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_common(BytesIO(), 1) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + assert not layout.propnames + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + assert not gg.properties, f'Fail on polygon {ii}' + + +def write_file_2(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 15) # XYRELATIVE record + + # POLYGON 0 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_0011) # 00PX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 4) # pointlist: g-delta + write_uint(buf, 8002) # (pointlist) dimension + write_uint(buf, 1000 << 2 | 0b11) # (pointlist) + write_sint(buf, 0) # (pointlist) + for _ in range(4000): + write_uint(buf, 10 << 2 | 0b01) # (pointlist) + write_sint(buf, 20) # (pointlist) + write_uint(buf, 10 << 2 | 0b11) # (pointlist) + write_sint(buf, 20) # (pointlist) + write_uint(buf, 1000 << 2 | 0b01) # (pointlist) + write_sint(buf, 0) # (pointlist) + write_sint(buf, 0) # geometry-x (absolute) + + buf.write(FOOTER) + return buf + + +def test_file_2() -> None: + buf = write_file_2(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert len(layout.cells[0].geometry) == 1 + + poly = layout.cells[0].geometry[0] + assert poly.layer == 2 + assert poly.datatype == 3 + assert poly.x == 0 + assert poly.y == 0 + assert len(poly.point_list) == 8002 + 1 + assert_equal(poly.point_list, + ([[-1000, 0]] + + [[(-1) ** nn * 10, 20] for nn in range(8000)] + + [[1000, 0], [0, -20 * 8000]])) + + +def test_file_3() -> None: + buf = write_file_common(BytesIO(), 3) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + + assert len(layout.propnames) == 1 + assert layout.propnames[0].string == 'PROP0' + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + msg = f'Fail on polygon {ii}' + assert len(gg.properties) == 1, msg + assert gg.properties[0].name == 0, msg + assert len(gg.properties[0].values) == 1, msg + assert gg.properties[0].values[0] * 5 == 1, msg + diff --git a/fatamorgana/test/test_files_properties.py b/fatamorgana/test/test_files_properties.py new file mode 100644 index 0000000..a49d7e1 --- /dev/null +++ b/fatamorgana/test/test_files_properties.py @@ -0,0 +1,1084 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + +def write_file_common(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + include_repetitions = variant in (2, 5) + + def var_byte(buf, byte): + if include_repetitions: + byte |= 0b0100 + write_byte(buf, byte) + + buf.write(HEADER) + + if variant in (1, 2): + write_uint(buf, 10) # PROPSTRING (explicit id) + write_bstring(buf, b'PropStringId12') + write_uint(buf, 12) # id + + write_uint(buf, 10) # PROPSTRING record (explicit id) + write_bstring(buf, b'Property string value for ID 13') + write_uint(buf, 13) # id + + write_uint(buf, 7) # PROPNAME record (implicit id 0) + write_bstring(buf, b'PROP0') + + write_uint(buf, 7) # PROPNAME record (implicit id 1) + write_bstring(buf, b'PROP1') + + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_uint(buf, 16) # XYRELATIVE record + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + var_byte(buf, 0b0110_0011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + if include_repetitions: + write_uint(buf, 1) # repetition (3x2 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 0) # (repetition) y-dimension + write_uint(buf, 300) # (repetition) x-spacing + write_uint(buf, 320) # (repetition) y-spacing + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_0100) # UUUU_VCNS + write_bstring(buf, b'PROPX') + + # RECTANGLE 1 + write_uint(buf, 20) # RECTANGLE record + var_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 1) # property value 0 (real type 1, negative int) + write_uint(buf, 5) # (real 1) + + # RECTANGLE 2 + write_uint(buf, 20) # RECTANGLE record + var_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0100_0110) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 8) # prop value 0 (unsigned int) + write_uint(buf, 25) # (prop value) + write_uint(buf, 9) # prop value 1 (signed int) + write_sint(buf, -124) # (prop value) + write_uint(buf, 10) # prop value 2 (a-string) + write_bstring(buf, b'PROP_VALUE2') + write_uint(buf, 13) # prop value 3 (propstring ref.) + write_uint(buf, 12) + + # RECTANGLE 3 + write_uint(buf, 20) # RECTANGLE record + var_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b1111_0000) # UUUU_VCNS + write_uint(buf, 3) # number of values + write_uint(buf, 0) # prop value 0 (unsigned int) + write_uint(buf, 25) # (prop value) + write_uint(buf, 9) # prop value 1 (signed int) + write_sint(buf, -124) # (prop value) + write_uint(buf, 14) # prop value 2 (propstring ref.) + write_uint(buf, 13) + + # RECTANGLE 4 + write_uint(buf, 20) # RECTANGLE record + var_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_1000) # UUUU_VCNS + + write_uint(buf, 15) # XYABSOLUTE record + + # TEXT 5 + write_uint(buf, 19) # TEXT record + var_byte(buf, 0b0101_1011) # 0CNX_YRTL + write_bstring(buf, b'A') # text-string + write_uint(buf, 2) # text-layer + write_uint(buf, 1) # text-datatype + write_sint(buf, 1000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 29) # PROPERTY (reuse) + + # PATH 6 + write_uint(buf, 22) # PATH record + var_byte(buf, 0b1111_1011) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 10) # half-width + write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE + write_sint(buf, 5) # (extension-scheme) + write_sint(buf, -5) # (extension-scheme) + write_uint(buf, 0) # pointlist (1-delta, horiz. first) + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 2000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 29) # PROPERTY (reuse) + + # POLYGON 7 + write_uint(buf, 21) # POLYGON record + var_byte(buf, 0b0011_1011) # 00PX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 0) # pointlist (1-delta, horiz. first) + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 3000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + if include_repetitions: + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 29) # PROPERTY (reuse) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_0110) # UUUU_VCNS + write_uint(buf, 1) # propname id + + if variant == 5: + write_uint(buf, 10) # PROPSTRING (explicit id) + write_bstring(buf, b'PropStringId12') + write_uint(buf, 12) # id + + write_uint(buf, 10) # PROPSTRING record (explicit id) + write_bstring(buf, b'Property string value for ID 13') + write_uint(buf, 13) # id + + write_uint(buf, 7) # PROPNAME record (implicit id 0) + write_bstring(buf, b'PROP0') + + write_uint(buf, 7) # PROPNAME record (implicit id 1) + write_bstring(buf, b'PROP1') + buf.write(FOOTER) + return buf + + +def common_geometry_tests(layout: OasisLayout) -> None: + geometry = layout.cells[0].geometry + assert len(geometry) == 8 + + for ii, gg in enumerate(geometry): + msg = f'Failed on element {ii}' + assert gg.x == [0, 0, 0, 0, 0, 1000, 2000, 3000][ii], msg + assert gg.y == [0, 1000, 2000, 3000, 4000, 0, 0, 0][ii], msg + + if ii == 5: + assert gg.layer == 2, msg + assert gg.datatype == 1, msg + else: + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + + if ii < 5: + assert gg.width == 100, msg + assert gg.height == 200, msg + assert geometry[5].string.string == 'A' + assert_equal(geometry[6].point_list, [(150, 0), (0, 50), (-50, 0)]) + assert_equal(geometry[7].point_list, [(150, 0), (0, 50), (-50, 0), + (0, 50), (-100, 0), (0, -100)]) + + +def common_property_tests(layout: OasisLayout) -> None: + geometry = layout.cells[0].geometry + assert len(geometry[0].properties) == 1 + assert geometry[0].properties[0].name.string == 'PROPX' + assert len(geometry[0].properties[0].values) == 0 + + assert len(geometry[1].properties) == 1 + assert geometry[1].properties[0].name == 0 + assert len(geometry[1].properties[0].values) == 1 + assert geometry[1].properties[0].values[0] == -5 + + assert len(geometry[2].properties) == 1 + assert geometry[2].properties[0].name == 0 + assert len(geometry[2].properties[0].values) == 4 + assert geometry[2].properties[0].values[0] == 25 + assert geometry[2].properties[0].values[1] == -124 + assert geometry[2].properties[0].values[2].string == 'PROP_VALUE2' + assert geometry[2].properties[0].values[3].ref == 12 + + for ii in range(3, 8): + msg = f'Failed on element {ii}' + assert geometry[ii].properties[0].name == 0, msg + assert len(geometry[ii].properties[0].values) == 3, msg + assert geometry[ii].properties[0].values[0] == 25, msg + assert geometry[ii].properties[0].values[1] == -124, msg + assert geometry[ii].properties[0].values[2].ref == 13, msg + + for ii in range(3, 7): + msg = f'Failed on element {ii}' + assert len(geometry[ii].properties) == 1, msg + + assert len(geometry[7].properties) == 2 + assert geometry[7].properties[1].name == 1 + + +def test_file_1() -> None: + buf = write_file_common(BytesIO(), 1) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + common_geometry_tests(layout) + common_property_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + msg = f'Failed on element {ii}' + assert gg.repetition is None, msg + + +def test_file_2() -> None: + buf = write_file_common(BytesIO(), 2) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + common_geometry_tests(layout) + common_property_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + msg = f'Failed on element {ii}' + assert gg.repetition.a_count == 3, msg + assert gg.repetition.b_count == 2, msg + assert gg.repetition.a_vector == [300, 0], msg + assert gg.repetition.b_vector == [0, 320], msg + + +def test_file_5() -> None: + buf = write_file_common(BytesIO(), 5) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + common_geometry_tests(layout) + common_property_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + msg = f'Failed on element {ii}' + assert gg.repetition.a_count == 3, msg + assert gg.repetition.b_count == 2, msg + assert gg.repetition.a_vector == [300, 0], msg + assert gg.repetition.b_vector == [0, 320], msg + + +def write_file_3(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 10) # PROPSTRING (explicit id) + write_bstring(buf, b'PropStringId12') + write_uint(buf, 12) # id + + write_uint(buf, 10) # PROPSTRING record (explicit id) + write_bstring(buf, b'Property string value for ID 13') + write_uint(buf, 13) # id + + write_uint(buf, 7) # PROPNAME record (implicit id 0) + write_bstring(buf, b'S_GDS_PROPERTY') + + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + write_uint(buf, 16) # XYRELATIVE record + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0010_0111) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 8) # property value 0 (unsigned int) + write_uint(buf, 25) # (...) + write_uint(buf, 10) # property value 1 (a-string) + write_bstring(buf, b'PROP_VALUE2') + + # RECTANGLE 1 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b1111_0001) # UUUU_VCNS + write_uint(buf, 2) # number of values + write_uint(buf, 8) # property value 0 (unsigned int) + write_uint(buf, 10) # (...) + write_uint(buf, 14) # property value 1 (prop-string ref.) + write_uint(buf, 13) # (...) + + # RECTANGLE 2 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_1001) # UUUU_VCNS + + # RECTANGLE 3 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + + write_uint(buf, 29) # PROPERTY (reuse) + + # RECTANGLE 4 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 0) # geometry-x (relative) + write_sint(buf, 1000) # geometry-y (relative) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_1001) # UUUU_VCNS + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0010_0111) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 8) # prop value 0 (unsigned int) + write_uint(buf, 25) # (...) + write_uint(buf, 10) # prop-value 1 (a-string) + write_bstring(buf, b'PROP_VALUE2') # (...) + + write_uint(buf, 15) # XYABSOLUTE record + + # TEXT 5 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0101_1011) # 0CNX_YRTL + write_bstring(buf, b'A') # text-string + write_uint(buf, 2) # text-layer + write_uint(buf, 1) # text-datatype + write_sint(buf, 1000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + + write_uint(buf, 29) # PROPERTY (reuse) + + # PATH 6 + write_uint(buf, 22) # PATH record + write_byte(buf, 0b1111_1011) # EWPX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 10) # half-width + write_byte(buf, 0b0000_1111) # extension-scheme 0000_SSEE + write_sint(buf, 5) # (extension-scheme) + write_sint(buf, -5) # (extension-scheme) + write_uint(buf, 0) # pointlist (1-delta, horiz. first) + write_uint(buf, 3) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 2000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + + write_uint(buf, 29) # PROPERTY (reuse) + + # POLYGON 7 + write_uint(buf, 21) # POLYGON record + write_byte(buf, 0b0011_1011) # 00PX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 0) # pointlist (1-delta, horiz. first) + write_uint(buf, 4) # (pointlist) dimension + write_sint(buf, 150) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, -50) # (pointlist) + write_sint(buf, 50) # (pointlist) + write_sint(buf, 3000) # geometry-x (absolute) + write_sint(buf, 0) # geometry-y (absolute) + + write_uint(buf, 29) # PROPERTY (reuse) + + buf.write(FOOTER) + return buf + + +def test_file_3() -> None: + buf = write_file_3(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'A' + assert not layout.cells[0].properties + + geometry = layout.cells[0].geometry + assert len(geometry) == 8 + for ii, gg in enumerate(geometry): + msg = f'Failed on element {ii}' + assert gg.x == [0, 0, 0, 0, 0, 1000, 2000, 3000][ii], msg + assert gg.y == [1000, 2000, 3000, 4000, 5000, 0, 0, 0][ii], msg + + if ii == 5: + assert gg.layer == 2, msg + assert gg.datatype == 1, msg + else: + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + + if ii < 5: + assert gg.width == 100, msg + assert gg.height == 200, msg + + assert gg.repetition is None, msg + assert geometry[5].string.string == 'A' + assert_equal(geometry[6].point_list, [(150, 0), (0, 50), (-50, 0)]) + assert_equal(geometry[7].point_list, [(150, 0), (0, 50), (-50, 0), + (0, 50), (-100, 0), (0, -100)]) + + assert len(geometry[0].properties) == 1 + assert geometry[0].properties[0].name == 0 + assert len(geometry[0].properties[0].values) == 2 + assert geometry[0].properties[0].values[0] == 25 + assert geometry[0].properties[0].values[1].string == 'PROP_VALUE2' + + for ii in range(1, 4): + msg = f'Failed on element {ii}' + assert len(geometry[ii].properties) == 1, msg + assert geometry[ii].properties[0].name == 0, msg + assert len(geometry[ii].properties[0].values) == 2, msg + assert geometry[ii].properties[0].values[0] == 10, msg + assert geometry[ii].properties[0].values[1].ref == 13, msg + + assert len(geometry[4].properties) == 2 + assert geometry[4].properties[0].name == 0 + assert geometry[4].properties[1].name == 0 + assert len(geometry[4].properties[0].values) == 2 + assert len(geometry[4].properties[1].values) == 2 + assert geometry[4].properties[0].values[0] == 10 + assert geometry[4].properties[0].values[1].ref == 13 + assert geometry[4].properties[1].values[0] == 25 + assert geometry[4].properties[1].values[1].string == 'PROP_VALUE2' + + for ii in range(5, 8): + msg = f'Failed on element {ii}' + assert len(geometry[ii].properties) == 1, msg + assert geometry[ii].properties[0].name == 0, msg + assert len(geometry[ii].properties[0].values) == 2, msg + assert geometry[ii].properties[0].values[0] == 25, msg + assert geometry[ii].properties[0].values[1].string == 'PROP_VALUE2', msg + + +def write_file_4_6(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 10) # PROPSTRING (explicit id) + write_bstring(buf, b'PropStringId12') + write_uint(buf, 12) # id + + write_uint(buf, 10) # PROPSTRING record (explicit id) + write_bstring(buf, b'Property string value for ID 13') + write_uint(buf, 13) # id + + if variant == 4: + write_uint(buf, 7) # PROPNAME record (implicit id 0) + write_bstring(buf, b'S_GDS_PROPERTY') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'A') # Cell name + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 300) # geometry-x (relative) + write_sint(buf, -400) # geometry-y (relative) + + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'TOP') # Cell name + + # PLACEMENT 0 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b1011_0000) # CNXY_RAAF + write_bstring(buf, b'A') # cell name + write_sint(buf, -300) # placement-x + write_sint(buf, 400) # placement-y + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0010_0111) # UUUU_VCNS + write_uint(buf, 0) # propname-id + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 25) # (...) + write_uint(buf, 10) # prop-value 1 (a-string) + write_bstring(buf, b'PROP_VALUE2') + + if variant == 6: + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0010_0111) # UUUU_VCNS + write_uint(buf, 0) # propname-id + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 26) # (...) + write_uint(buf, 10) # prop-value 1 (a-string) + write_bstring(buf, b'PROP_VALUE26') + + # PLACEMENT 1 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_0000) # CNXY_RAAF + write_sint(buf, 0) # placement-x + if variant == 4: + write_sint(buf, 200) # placement-y + else: + write_sint(buf, 400) # placement-y + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b1111_0001) # UUUU_VCNS + write_uint(buf, 2) # number of values + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 10) # (...) + write_uint(buf, 14) # prop-value 1 (prop-string ref.) + write_uint(buf, 13) # (...) + + # PLACEMENT 2 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0001_0000) # CNXY_RAAF + write_sint(buf, 400) # placement-y + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_1001) # UUUU_VCNS + + # PLACEMENT 3 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0010_0000) # CNXY_RAAF + write_sint(buf, 300) # placement-x + + write_uint(buf, 29) # PROPERTY (reuse) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 4 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_0001) # CNXY_RAAF + write_sint(buf, 700) # placement-x (absolute) + write_sint(buf, 400) # placement-y (absolute) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0000_1001) # UUUU_VCNS + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 5 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0001_0010) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0010_0111) # UUUU_VCNS + write_uint(buf, 0) # propname-id + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 25) # (...) + write_uint(buf, 10) # prop-value 1 (a-string) + write_bstring(buf, b'PROP_VALUE2') + + # PLACEMENT 6 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0001_0011) # CNXY_RAAF + write_sint(buf, 1000) # placement-y (relative) + + write_uint(buf, 29) # PROPERTY (reuse) + + write_uint(buf, 15) # XYABSOLUTE record + + # PLACEMENT 7 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x (absolute) + write_sint(buf, 0) # placement-y (absolute) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 300) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 29) # PROPERTY (reuse) + + write_uint(buf, 16) # XYRELATIVE record + + # PLACEMENT 8 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x + write_sint(buf, 0) # placement-y + write_uint(buf, 0) # repetition (reuse) + + write_uint(buf, 29) # PROPERTY (reuse) + + # PLACEMENT 9 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x + write_sint(buf, 0) # placement-y + write_uint(buf, 2) # repetition (3 cols.) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, 320) # (repetition) offset + + write_uint(buf, 29) # PROPERTY (reuse) + + # PLACEMENT 10 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x + write_sint(buf, 0) # placement-y + write_uint(buf, 3) # repetition (4 rows) + write_uint(buf, 2) # (repetition) dimension + write_uint(buf, 310) # (repetition) offset + + write_uint(buf, 29) # PROPERTY (reuse) + + # PLACEMENT 11 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x + write_sint(buf, 0) # placement-y + write_uint(buf, 4) # repetition (4 arbitrary cols.) + write_uint(buf, 2) # (repetition) dimension + write_uint(buf, 320) # (repetition) + write_uint(buf, 330) # (repetition) + write_uint(buf, 340) # (repetition) + + write_uint(buf, 29) # PROPERTY (reuse) + + # PLACEMENT 12 + write_uint(buf, 17) # PLACEMENT record (no mag, manhattan) + write_byte(buf, 0b0011_1111) # CNXY_RAAF + write_sint(buf, 2000) # placement-x + write_sint(buf, 0) # placement-y + write_uint(buf, 8) # repetition (3x4 matrix, arbitrary vectors) + write_uint(buf, 1) # (repetition) n-dimension + write_uint(buf, 2) # (repetition) m-dimension + write_uint(buf, 310 << 2 | 0b01) # (repetition) n-displacement g-delta (310, 320) + write_sint(buf, 320) + write_uint(buf, 330 << 4 | 0b1010) # (repetition) m-dispalcement g-delta 330/northwest = (-330, 330) + + write_uint(buf, 29) # PROPERTY (reuse) + + if variant == 6: + write_uint(buf, 7) # PROPNAME record (implicit id 0) + write_bstring(buf, b'S_GDS_PROPERTY') + + buf.write(FOOTER) + return buf + + +def test_file_4() -> None: + ''' + ''' + buf = write_file_4_6(BytesIO(), 4) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 2 + assert layout.cells[0].name.string == 'A' + assert layout.cells[1].name.string == 'TOP' + assert not layout.cells[0].properties + assert not layout.cells[1].properties + assert not layout.cells[0].placements + assert not layout.cells[1].geometry + + geometry = layout.cells[0].geometry + assert len(geometry) == 1 + assert geometry[0].x == 300 + assert geometry[0].y == -400 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + + assert layout.propstrings[12].string == 'PropStringId12' + assert layout.propstrings[13].string == 'Property string value for ID 13' + assert layout.propnames[0].string == 'S_GDS_PROPERTY' + + placements = layout.cells[1].placements + assert len(placements) == 13 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii}' + assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg + assert pp.y == [400, 200, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg + + if ii == 4 or 6 <= ii: + assert pp.flip, msg + else: + assert not pp.flip, msg + + if ii < 7: + assert pp.repetition is None, msg + + assert len(placements[0].properties) == 1 + assert placements[0].properties[0].name == 0 + assert len(placements[0].properties[0].values) == 2 + assert placements[0].properties[0].values[0] == 25 + assert placements[0].properties[0].values[1].string == 'PROP_VALUE2' + + for ii in range(1, 5): + msg = f'Failed on placement {ii}' + assert len(placements[ii].properties) == 1, msg + assert placements[ii].properties[0].name == 0, msg + assert len(placements[ii].properties[0].values) == 2, msg + assert placements[ii].properties[0].values[0] == 10, msg + assert placements[ii].properties[0].values[1].ref == 13, msg + + for ii in range(5, 13): + msg = f'Failed on placement {ii}' + assert len(placements[ii].properties) == 1, msg + assert placements[ii].properties[0].name == 0, msg + assert len(placements[ii].properties[0].values) == 2, msg + assert placements[ii].properties[0].values[0] == 25, msg + assert placements[ii].properties[0].values[1].string == 'PROP_VALUE2', msg + + +def test_file_6() -> None: + ''' + ''' + buf = write_file_4_6(BytesIO(), 6) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.cells) == 2 + assert layout.cells[0].name.string == 'A' + assert layout.cells[1].name.string == 'TOP' + assert not layout.cells[0].properties + assert not layout.cells[1].properties + assert not layout.cells[0].placements + assert not layout.cells[1].geometry + + geometry = layout.cells[0].geometry + assert len(geometry) == 1 + assert geometry[0].x == 300 + assert geometry[0].y == -400 + assert geometry[0].layer == 1 + assert geometry[0].datatype == 2 + assert geometry[0].width == 100 + assert geometry[0].height == 200 + + assert layout.propstrings[12].string == 'PropStringId12' + assert layout.propstrings[13].string == 'Property string value for ID 13' + assert layout.propnames[0].string == 'S_GDS_PROPERTY' + + placements = layout.cells[1].placements + assert len(placements) == 13 + for ii, pp in enumerate(placements): + msg = f'Failed on placement {ii}' + assert pp.x == [-300, 0, 0, 300, 700, 700, 700, 2000, 4000, 6000, 8000, 10000, 12000][ii], msg + assert pp.y == [400, 400, 400, 400, 400, 1400, 2400, 0, 0, 0, 0, 0, 0][ii], msg + + if ii == 4 or 6 <= ii: + assert pp.flip, msg + else: + assert not pp.flip, msg + + if ii < 7: + assert pp.repetition is None, msg + + assert len(placements[0].properties) == 2 + assert placements[0].properties[0].name == 0 + assert len(placements[0].properties[0].values) == 2 + assert placements[0].properties[0].values[0] == 25 + assert placements[0].properties[0].values[1].string == 'PROP_VALUE2' + assert placements[0].properties[1].name == 0 + assert len(placements[0].properties[1].values) == 2 + assert placements[0].properties[1].values[0] == 26 + assert placements[0].properties[1].values[1].string == 'PROP_VALUE26' + + for ii in range(1, 5): + msg = f'Failed on placement {ii}' + assert len(placements[ii].properties) == 1, msg + assert placements[ii].properties[0].name == 0, msg + assert len(placements[ii].properties[0].values) == 2, msg + assert placements[ii].properties[0].values[0] == 10, msg + assert placements[ii].properties[0].values[1].ref == 13, msg + + for ii in range(5, 13): + msg = f'Failed on placement {ii}' + assert len(placements[ii].properties) == 1, msg + assert placements[ii].properties[0].name == 0, msg + assert len(placements[ii].properties[0].values) == 2, msg + assert placements[ii].properties[0].values[0] == 25, msg + assert placements[ii].properties[0].values[1].string == 'PROP_VALUE2', msg + + +def write_file_7_8_9(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_0100) # UUUU_VCNS + write_bstring(buf, b'FileProp1') # property name + write_uint(buf, 10) # prop-value 0 (a-string) + write_bstring(buf, b'FileProp1Value') + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 13) # prop-name reference + write_uint(buf, 10) # prop-value 0 (a-string) + write_bstring(buf, b'FileProp1Value') + + write_uint(buf, 8) # PROPNAME record (explicit id) + write_bstring(buf, b'FileProp2') + write_uint(buf, 13) # id + + # associated with PROPNAME? + write_uint(buf, 28) # PROPERTY record + if variant == 8: + # Will give an error since the value modal variable is reset by PROPNAME_ID + write_byte(buf, 0b0001_1110) # UUUU_VCNS + else: + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 13) # prop-name reference + if variant != 8: + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 17) # (...) + + write_uint(buf, 10) # PROPSTRING (explicit id) + write_bstring(buf, b'FileProp2Value') + write_uint(buf, 12) # id + + # associated with PROPSTRING? + write_uint(buf, 28) # PROPERTY record + if variant == 9: + # Will give an error since the value modal variable is unset + write_byte(buf, 0b0001_1110) # UUUU_VCNS + else: + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 13) # prop-name reference + if variant != 9: + write_uint(buf, 8) # prop-value 0 (unsigned int) + write_uint(buf, 42) # (...) + + write_uint(buf, 3) # CELLNAME record (implicit id 0) + write_bstring(buf, b'A') + + # associated with cell A, through CELLNAME # TODO + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_0100) # UUUU_VCNS + write_bstring(buf, b'CellProp0') # prop name + write_uint(buf, 10) # prop-value 0 (a-string) + write_bstring(buf, b'CPValue0') + + + write_uint(buf, 13) # CELL record (name ref.) + write_uint(buf, 0) # Cell name 0 (XYZ) + + # associated with cell A + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_0100) # UUUU_VCNS + write_bstring(buf, b'CellProp1') # prop name + write_uint(buf, 10) # prop-value 0 (a-string) + write_bstring(buf, b'CPValue') + + write_uint(buf, 28) # PROPERTY record + write_byte(buf, 0b0001_1100) # UUUU_VCNS + write_bstring(buf, b'CellProp2') # prop name + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 300) # geometry-x + write_sint(buf, -400) # geometry-y + + buf.write(FOOTER) + return buf + + + +def test_file_7() -> None: + buf = write_file_7_8_9(BytesIO(), 7) + + buf.seek(0) + layout = OasisLayout.read(buf) + + assert len(layout.properties) == 4 + assert layout.properties[0].name.string == 'FileProp1' + assert layout.properties[1].name == 13 + assert layout.properties[2].name == 13 + assert layout.properties[3].name == 13 + assert len(layout.properties[0].values) == 1 + assert len(layout.properties[1].values) == 1 + assert len(layout.properties[2].values) == 1 + assert len(layout.properties[3].values) == 1 + assert layout.properties[1].values[0].string == 'FileProp1Value' + assert layout.properties[1].values[0].string == 'FileProp1Value' + assert layout.properties[2].values[0] == 17 + assert layout.properties[3].values[0] == 42 + + assert len(layout.cells) == 1 + cprops = layout.cells[0].properties + assert len(cprops) == 2 + assert cprops[0].name.string == 'CellProp1' + assert cprops[1].name.string == 'CellProp2' + assert len(cprops[0].values) == 1 + assert len(cprops[1].values) == 1 + assert cprops[0].values[0].string == 'CPValue' + assert cprops[1].values[0].string == 'CPValue' + + assert len(layout.cellnames) == 1 + cnprops = layout.cellnames[0].properties + assert len(cnprops) == 1 + assert cnprops[0].name.string == 'CellProp0' + assert len(cnprops[0].values) == 1 + assert cnprops[0].values[0].string == 'CPValue0' + + # TODO Document that cell properties can be attached to both + # the cell and the cellname + + # TODO Document that value count is ignored when using modal + + +def test_file_8() -> None: + ''' + ''' + buf = write_file_7_8_9(BytesIO(), 8) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) + + +def test_file_9() -> None: + ''' + ''' + buf = write_file_7_8_9(BytesIO(), 9) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) diff --git a/fatamorgana/test/test_files_rectangles.py b/fatamorgana/test/test_files_rectangles.py new file mode 100644 index 0000000..f47503f --- /dev/null +++ b/fatamorgana/test/test_files_rectangles.py @@ -0,0 +1,279 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABC' + assert not layout.cells[0].properties + + geometry = layout.cells[0].geometry + + assert geometry[0].x == 300 + assert geometry[0].y == -400 + assert geometry[1].x == 400 + assert geometry[1].y == -500 + assert geometry[2].x == 600 + assert geometry[2].y == -300 + assert geometry[3].x == 800 + assert geometry[3].y == -300 + + assert geometry[4].y == -600 + assert geometry[5].y == -900 + assert geometry[6].y == -1200 + assert geometry[7].y == -1500 + assert geometry[8].y == -1800 + assert geometry[9].y == 500 + assert geometry[10].y == 2000 + + for ii, gg in enumerate(geometry[3:]): + assert gg.x == 800, f'Failed on rectangle {ii + 3}' + + for ii, gg in enumerate(geometry): + msg = f'Failed on rectangle {ii}' + if ii < 4: + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + else: + assert gg.layer == 2, msg + assert gg.datatype == 3, msg + + if ii < 7: + assert gg.width == 100, msg + assert gg.height == 200, msg + elif ii == 7: + assert gg.width == 150, msg + assert gg.height is None, msg + else: + assert gg.width == 150, msg + assert gg.height == 150, msg + + if ii < 9: + assert gg.repetition is None, msg + + assert geometry[9].repetition.a_count == 3 + assert geometry[9].repetition.b_count == 4 + assert geometry[9].repetition.a_vector == [200, 0] + assert geometry[9].repetition.b_vector == [0, 300] + + assert geometry[10].repetition.x_displacements == [200, 300] + + +def write_file_common(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + ''' + assert variant in (1, 2), 'Error in test!!' + + buf.write(HEADER) + + if variant == 2: + write_uint(buf, 7) # PROPNAME record (implict id 0) + write_bstring(buf, b'PROP0') # property name + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + # RECTANGLE 0 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 300) # geometry-x (absolute) + write_sint(buf, -400) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 0 + write_uint(buf, 28) # PROPERTY record (explicit) + write_byte(buf, 0b0001_0110) # UUUU_VCNS + write_uint(buf, 0) # propname id + write_uint(buf, 2) # property value (real: positive reciprocal) + write_uint(buf, 5) # (real) 1/5 + + write_uint(buf, 16) # XYRELATIVE record + + # RECTANGLE 1 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 100) # geometry-x (relative) + write_sint(buf, -100) # geometry-y (relative) + + if variant == 2: + # PROPERTY 1 + write_uint(buf, 29) # PROPERTY record (repeat) + + write_uint(buf, 15) # XYABSOLUTE record + + # RECTANGLE 2 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_1011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 600) # geometry-x (absolute) + write_sint(buf, -300) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 2 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 3 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0111_0011) # SWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, 800) # geometry-x (absolute) + + if variant == 2: + # PROPERTY 3 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 4 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0110_1011) # SWHX_YRDL + write_uint(buf, 2) # layer + write_uint(buf, 3) # datatype + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, -600) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 4 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 5 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0110_1000) # SWHX_YRDL + write_uint(buf, 100) # width + write_uint(buf, 200) # height + write_sint(buf, -900) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 5 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 6 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_1000) # SWHX_YRDL + write_sint(buf, -1200) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 6 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 7 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b1100_1000) # SWHX_YRDL + write_uint(buf, 150) # width + write_sint(buf, -1500) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 7 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 8 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_1000) # SWHX_YRDL + write_sint(buf, -1800) # geometry-y (absolute) + + if variant == 2: + # PROPERTY 8 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 9 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_1100) # SWHX_YRDL + write_sint(buf, 500) # geometry-y (absolute) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + if variant == 2: + # PROPERTY 9 + write_uint(buf, 29) # PROPERTY record (repeat) + + # RECTANGLE 10 + write_uint(buf, 20) # RECTANGLE record + write_byte(buf, 0b0000_1100) # SWHX_YRDL + write_sint(buf, 2000) # geometry-y (absolute) + write_uint(buf, 4) # repetition (3 arbitrary cols.) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, 200) # (repetition) x-delta + write_uint(buf, 300) # (repetition) x-delta + + if variant == 2: + # PROPERTY 10 + write_uint(buf, 29) # PROPERTY record (repeat) + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_common(BytesIO(), 1) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + assert not layout.propnames + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + assert not gg.properties, f'Fail on rectangle {ii}' + + +def test_file_2() -> None: + buf = write_file_common(BytesIO(), 2) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + assert len(layout.propnames) == 1 + assert layout.propnames[0].string == 'PROP0' + + geometry = layout.cells[0].geometry + + for ii, gg in enumerate(geometry): + msg = f'Failed on rectangle {ii}' + assert len(gg.properties) == 1, msg + prop = gg.properties[0] + + assert prop.name == 0, msg + assert len(prop.values) == 1, msg + assert prop.values[0].numerator == 1, msg + assert prop.values[0].denominator == 5, msg + diff --git a/fatamorgana/test/test_files_texts.py b/fatamorgana/test/test_files_texts.py new file mode 100644 index 0000000..c23877c --- /dev/null +++ b/fatamorgana/test/test_files_texts.py @@ -0,0 +1,738 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte +from ..basic import InvalidRecordError, InvalidDataError +from ..basic import GridRepetition, ArbitraryRepetition +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.cellnames + assert not layout.propstrings + assert not layout.layers + + +def common_tests(layout: OasisLayout) -> None: + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABC' + + geometry = layout.cells[0].geometry + + geometry[0].layer == 1 + geometry[0].datatype == 2 + for ii, gg in enumerate(geometry[1:]): + assert gg.layer == 2, f'textstring #{ii + 1}' + assert gg.datatype == 1, f'textstring #{ii + 1}' + + assert geometry[0].x == 100 + assert geometry[0].y == -200 + assert geometry[1].x == 200 + assert geometry[1].y == -400 + assert geometry[2].y == -400 + for ii, gg in enumerate(geometry[2:]): + assert gg.x == 300, f'textstring #{ii + 2}' + + for ii, gg in enumerate(geometry[3:]): + assert gg.y == -300 - 200 * ii, f'textstring #{ii + 3}' + + for ii, gg in enumerate(geometry): + if ii < 4: + assert gg.repetition is None, f'textstring #{ii}' + elif ii in (4, 5, 6, 7, 12, 13, 14, 15): + assert isinstance(gg.repetition, GridRepetition), f'textstring #{ii}' + else: + assert isinstance(gg.repetition, ArbitraryRepetition), f'textstring #{ii}' + + for ii in (4, 5): + assert geometry[ii].repetition.a_count == 3, f'textstring #{ii}' + assert geometry[ii].repetition.b_count == 4, f'textstring #{ii}' + assert geometry[ii].repetition.a_vector == [10, 0], f'textstring #{ii}' + assert geometry[ii].repetition.b_vector == [0, 12], f'textstring #{ii}' + + assert geometry[6].repetition.a_count == 3 + assert geometry[6].repetition.a_vector == [10, 0] + + assert geometry[7].repetition.a_count == 4 + assert geometry[7].repetition.a_vector == [0, 12] + + assert geometry[8].repetition.x_displacements == [12, 13, 14] + assert geometry[9].repetition.x_displacements == [4 * 3, 5 * 3, 6 * 3] + assert geometry[10].repetition.y_displacements == [10, 11] + assert geometry[11].repetition.y_displacements == [2 * 5, 3 * 5] + + assert geometry[12].repetition.a_count == 3 + assert geometry[12].repetition.b_count == 4 + assert geometry[12].repetition.a_vector == [10, 0] + assert geometry[12].repetition.b_vector == [-11, -12] + + assert geometry[13].repetition.a_count == 3 + assert geometry[13].repetition.b_count == 4 + assert geometry[13].repetition.a_vector == [11, 12] + assert geometry[13].repetition.b_vector == [-10, 10] + + assert geometry[14].repetition.a_count == 3 + assert geometry[14].repetition.b_count == None + assert geometry[14].repetition.a_vector == [11, 12] + assert geometry[14].repetition.b_vector is None + + assert geometry[15].repetition.a_count == 4 + assert geometry[15].repetition.b_count == None + assert geometry[15].repetition.a_vector == [-10, 10] + assert geometry[15].repetition.b_vector is None + + assert geometry[17].repetition.x_displacements == [-11, 10] + assert geometry[17].repetition.y_displacements == [12, -10] + + assert geometry[19].repetition.x_displacements == [-12, 9] + assert geometry[19].repetition.y_displacements == [12, -9] + + +def write_file_common(buf: BufferedIOBase, variant: int) -> BufferedIOBase: + ''' + Single cell with explicit name 'XYZ' + ''' + assert variant in (1, 2, 5, 12), 'Error in test!!' + + buf.write(HEADER) + + if variant == 2: + write_uint(buf, 6) # TEXTSTRING record (explicit id) + write_bstring(buf, b'A') + write_uint(buf, 1) # id + + write_uint(buf, 6) # TEXTSTRING record (explicit id) + write_bstring(buf, b'B') + write_uint(buf, 2) # id + elif variant == 5: + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 5) # TEXTSTRING record (implicit id 1) + write_bstring(buf, b'B') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + # TEXT 0 + write_uint(buf, 19) # TEXT record + if variant == 1: + write_byte(buf, 0b0101_1011) # 0CNX_YRTL + write_bstring(buf, b'TEXT_ABC') # text string + elif variant in (2, 5, 12): + write_byte(buf, 0b0111_1011) # 0CNX_YRTL + write_uint(buf, 1) # textstring id + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + write_uint(buf, 16) # XYRELATIVE + + # TEXT 1 + write_uint(buf, 19) # TEXT record + if variant == 1: + write_byte(buf, 0b0101_1011) # 0CNX_YRTL + write_bstring(buf, b'TEXT_ABC') # text string + elif variant in (2, 12): + write_byte(buf, 0b0111_1011) # 0CNX_YRTL + write_uint(buf, 2) # textstring id + elif variant == 5: + write_byte(buf, 0b0111_1011) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 2) # layer + write_uint(buf, 1) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + write_uint(buf, 15) # XYABSOLUTE + + # TEXT 2 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_0000) # 0CNX_YRTL + write_sint(buf, 300) # x + + # TEXT 3 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1000) # 0CNX_YRTL + write_sint(buf, -300) # y + + write_uint(buf, 16) # XYRELATIVE + + # TEXT 4 + write_uint(buf, 19) # TEXT record + if variant == 1: + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + elif variant in (2, 5, 12): + write_byte(buf, 0b0110_1100) # 0CNX_YRTL + write_uint(buf, 1) # textstring id + write_sint(buf, -200) # y + write_uint(buf, 1) # repetition (3x4) + write_uint(buf, 1) # (repetition) + write_uint(buf, 2) # (repetition) + write_uint(buf, 10) # (repetition) + write_uint(buf, 12) # (repetition) + + # TEXT 5 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 0) # repetition (reuse) + + # TEXT 6 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 2) # repetition (3 cols.) + write_uint(buf, 1) # (repetition) + write_uint(buf, 10) # (repetition) + + # TEXT 7 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 3) # repetition (4 cols.) + write_uint(buf, 2) # (repetition) + write_uint(buf, 12) # (repetition) + + # TEXT 8 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 4) # repetition (4 arbitrary cols.) + write_uint(buf, 2) # (repetition) + write_uint(buf, 12) # (repetition) + write_uint(buf, 13) # (repetition) + write_uint(buf, 14) # (repetition) + + # TEXT 9 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 5) # repetition (4 arbitrary cols., grid 3) + write_uint(buf, 2) # (repetition) + write_uint(buf, 3) # (repetition) + write_uint(buf, 4) # (repetition) + write_uint(buf, 5) # (repetition) + write_uint(buf, 6) # (repetition) + + # TEXT 10 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 6) # repetition (4 arbitrary cols., grid 3) + write_uint(buf, 1) # (repetition) + write_uint(buf, 10) # (repetition) + write_uint(buf, 11) # (repetition) + + # TEXT 11 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 7) # repetition (3 arbitrary cols., grid 5) + write_uint(buf, 1) # (repetition) + write_uint(buf, 5) # (repetition) + write_uint(buf, 2) # (repetition) + write_uint(buf, 3) # (repetition) + + # TEXT 12 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 8) # repetition (3x4 matrix w/arb. vectors) + write_uint(buf, 1) # (repetition) n-dimension + write_uint(buf, 2) # (repetition) m-dimension + write_uint(buf, (10 << 4) | 0b0000) # (repetition) n-displacement g-delta: 10/east = (10, 0) + write_uint(buf, (11 << 2) | 0b11) # (repetition) m-displacement g-delta: (-11, -12) + write_sint(buf, -12) # (repetition g-delta) + + # TEXT 13 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 8) # repetition (3x4 matrix w/arb. vectors) + write_uint(buf, 1) # (repetition) n-dimension + write_uint(buf, 2) # (repetition) m-dimension + write_uint(buf, (11 << 2) | 0b01) # (repetition) n-displacement g-delta: (11, 12) + write_sint(buf, 12) + write_uint(buf, (10 << 4) | 0b1010) # (repetition) n-displacement g-delta: 10/northwest = (-10, 10) + + # TEXT 14 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 9) # repetition (3x arb. vector) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, (11 << 2) | 0b01) # (repetition) n-displacement g-delta: (11, 12) + write_sint(buf, 12) # (repetition g-delta) + + # TEXT 15 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 9) # repetition (4x arb. vector) + write_uint(buf, 2) # (repetition) dimension + write_uint(buf, (10 << 4) | 0b1010) # (repetition) n-displacement g-delta: 10/northwest = (-10, 10) + + # TEXT 16 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 10) # repetition (9x / 8 arb. displacements) + write_uint(buf, 7) # (repetition) dimension + write_uint(buf, (10 << 4) | 0b0000) # (repetition) g-delta: 10/east = (10, 0) + write_uint(buf, (10 << 4) | 0b0010) # (repetition) g-delta: 10/north = (0, 10) + write_uint(buf, (10 << 4) | 0b0100) # (repetition) g-delta: 10/west = (-10, 0) + if variant == 12: + write_uint(buf, (10 << 4) | 0b0110) # (repetition) g-delta: 10/south = (0, -10) + else: + write_uint(buf, (40 << 4) | 0b0110) # (repetition) g-delta: 40/south = (0, -40) + write_uint(buf, (10 << 4) | 0b1000) # (repetition) g-delta: 10/northeast = (10, 10) + write_uint(buf, (10 << 4) | 0b1010) # (repetition) g-delta: 10/northwest = (-10, 10) + write_uint(buf, (10 << 4) | 0b1100) # (repetition) g-delta: 10/southwest = (-10, -10) + if variant == 12: + write_uint(buf, (10 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (10, -10) + else: + write_uint(buf, (20 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (20, -20) + + # TEXT 17 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 10) # repetition (3x / 2 arb. displacements) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, (11 << 2) | 0b11) # (repetition) g-delta: (-11, 12) + write_sint(buf, 12) # (repetition g-delta) + write_uint(buf, (10 << 4) | 0b1110) # (repetition) n-displacement g-delta: 10/southeast = (10, -10) + + # TEXT 18 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 11) # repetition (9x / grid 2 / 8 arb. displacements) + write_uint(buf, 7) # (repetition) dimension (9) + write_uint(buf, 2) # (repetition) grid + write_uint(buf, ( 5 << 4) | 0b0000) # (repetition) g-delta: 10/east = (10, 0) + write_uint(buf, ( 5 << 4) | 0b0010) # (repetition) g-delta: 10/north = (0, 10) + write_uint(buf, ( 5 << 4) | 0b0100) # (repetition) g-delta: 10/west = (-10, 0) + if variant == 12: + write_uint(buf, (5 << 4) | 0b0110) # (repetition) g-delta: 10/south = (0, -10) + else: + write_uint(buf, (20 << 4) | 0b0110) # (repetition) g-delta: 40/south = (0, -40) + write_uint(buf, ( 5 << 4) | 0b1000) # (repetition) g-delta: 10/northeast = (10, 10) + write_uint(buf, ( 5 << 4) | 0b1010) # (repetition) g-delta: 10/northwest = (-10, 10) + write_uint(buf, ( 5 << 4) | 0b1100) # (repetition) g-delta: 10/southwest = (-10, -10) + if variant == 12: + write_uint(buf, (5 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (-10, -10) + else: + write_uint(buf, (10 << 4) | 0b1110) # (repetition) g-delta: 20/southeast = (-20, -20) + + # TEXT 19 + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0000_1100) # 0CNX_YRTL + write_sint(buf, -200) # y + write_uint(buf, 11) # repetition (3x / grid 3 / 2 arb. displacements) + write_uint(buf, 1) # (repetition) dimension + write_uint(buf, 3) # (repetition) grid + write_uint(buf, (4 << 2) | 0b11) # (repetition) g-delta: (-12, 12) + write_sint(buf, 4) # (repetition g-delta) + write_uint(buf, (3 << 4) | 0b1110) # (repetition) n-displacement g-delta: 9/southeast = (9, -9) + + if variant == 12: + write_uint(buf, 6) # TEXTSTRING record (explicit id) + write_bstring(buf, b'A') + write_uint(buf, 1) # id + + write_uint(buf, 6) # TEXTSTRING record (explicit id) + write_bstring(buf, b'B') + write_uint(buf, 2) # id + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_common(BytesIO(), 1) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + assert gg.string.string == 'TEXT_ABC', f'textstring #{ii}' + + assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + +def test_file_2() -> None: + buf = write_file_common(BytesIO(), 2) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + if ii in (1, 2, 3): + assert gg.string == 2, f'textstring #{ii}' + else: + assert gg.string == 1, f'textstring #{ii}' + + assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + assert layout.textstrings[1].string == 'A' + assert layout.textstrings[2].string == 'B' + + +def test_file_5() -> None: + buf = write_file_common(BytesIO(), 5) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + if ii in (1, 2, 3): + assert gg.string == 0, f'textstring #{ii}' + else: + assert gg.string == 1, f'textstring #{ii}' + + assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[16].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 20] + assert geometry[18].repetition.y_displacements == [0, 10, 0, -40, 10, 10, -10, -20] + + assert layout.textstrings[0].string == 'A' + assert layout.textstrings[1].string == 'B' + + +def test_file_12() -> None: + buf = write_file_common(BytesIO(), 12) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + common_tests(layout) + + geometry = layout.cells[0].geometry + for ii, gg in enumerate(geometry): + if ii in (1, 2, 3): + assert gg.string == 2, f'textstring #{ii}' + else: + assert gg.string == 1, f'textstring #{ii}' + + assert geometry[16].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 10] + assert geometry[16].repetition.y_displacements == [0, 10, 0, -10, 10, 10, -10, -10] + + assert geometry[18].repetition.x_displacements == [10, 0, -10, 0, 10, -10, -10, 10] + assert geometry[18].repetition.y_displacements == [0, 10, 0, -10, 10, 10, -10, -10] + + assert layout.textstrings[1].string == 'A' + assert layout.textstrings[2].string == 'B' + + +def write_file_3(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with one textstring with explicit id, and one with an implicit id. + Should fail. + ''' + buf.write(HEADER) + + write_uint(buf, 6) # TEXTSTRING record (explicit id) + write_bstring(buf, b'A') + write_uint(buf, 1) # id + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) (FAIL due to mix) + write_bstring(buf, b'B') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_1011) # 0CNX_YRTL + write_uint(buf, 1) # textstring id + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_3() -> None: + buf = write_file_3(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidRecordError): + layout = OasisLayout.read(buf) + + +def write_file_4(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with a TEXT record that references a non-existent TEXTSTRING + + TODO add an optional check for valid references + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 5) # TEXTSTRING record (implicit id 1) + write_bstring(buf, b'B') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_1011) # 0CNX_YRTL + write_uint(buf, 2) # textstring id # INVALID ID + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_4() -> None: + buf = write_file_4(BytesIO()) + + buf.seek(0) +# with pytest.raises(InvalidRecordError): + layout = OasisLayout.read(buf) + + # TODO: check for invalid textstring references + base_tests(layout) + + +def write_file_6(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses an un-filled modal for the repetition + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_1111) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + write_uint(buf, 0) # reuse repetition (FAIL due to empty modal) + + buf.write(FOOTER) + return buf + + +def test_file_6() -> None: + buf = write_file_6(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) + + +def write_file_7(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses an un-filled modal for the layer + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_1010) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_7() -> None: + buf = write_file_7(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) + + +def write_file_8(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses an un-filled modal for the datatype + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_1001) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 1) # layer + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_8() -> None: + buf = write_file_8(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) + + +def write_file_9(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses a default modal for the x coordinate + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0110_1011) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_9() -> None: + buf = write_file_9(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + text = layout.cells[0].geometry[0] + assert text.x == 0 + assert text.layer == 1 + assert text.datatype == 2 + assert text.y == -200 + + +def write_file_10(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses a default modal for the y coordinate + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0111_0011) # 0CNX_YRTL + write_uint(buf, 0) # textstring id + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + + buf.write(FOOTER) + return buf + + +def test_file_10() -> None: + buf = write_file_10(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + text = layout.cells[0].geometry[0] + assert text.y == 0 + assert text.layer == 1 + assert text.datatype == 2 + assert text.x == 100 + + +def write_file_11(buf: BufferedIOBase) -> BufferedIOBase: + ''' + File with TEXT record that uses an un-filled modal for the text string + ''' + buf.write(HEADER) + + write_uint(buf, 5) # TEXTSTRING record (implicit id 0) + write_bstring(buf, b'A') + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + write_uint(buf, 19) # TEXT record + write_byte(buf, 0b0001_1011) # 0CNX_YRTL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_sint(buf, 100) # x + write_sint(buf, -200) # y + + buf.write(FOOTER) + return buf + + +def test_file_11() -> None: + buf = write_file_11(BytesIO()) + + buf.seek(0) + with pytest.raises(InvalidDataError): + layout = OasisLayout.read(buf) diff --git a/fatamorgana/test/test_files_trapezoids.py b/fatamorgana/test/test_files_trapezoids.py new file mode 100644 index 0000000..1bd67e1 --- /dev/null +++ b/fatamorgana/test/test_files_trapezoids.py @@ -0,0 +1,234 @@ +# type: ignore + +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore +import numpy +from numpy.testing import assert_equal + +from .utils import HEADER, FOOTER +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring, write_byte, PathExtensionScheme +from ..basic import InvalidRecordError, InvalidDataError +from ..main import OasisLayout + + +def base_tests(layout: OasisLayout) -> None: + assert layout.version.string == '1.0' + assert layout.unit == 1000 + assert layout.validation.checksum_type == 0 + + assert not layout.properties + assert not layout.propnames + assert not layout.xnames + assert not layout.textstrings + assert not layout.cellnames + assert not layout.layers + + assert len(layout.cells) == 1 + assert layout.cells[0].name.string == 'ABC' + assert not layout.cells[0].properties + + +def write_file_1(buf: BufferedIOBase) -> BufferedIOBase: + ''' + ''' + buf.write(HEADER) + + write_uint(buf, 14) # CELL record (explicit) + write_bstring(buf, b'ABC') # Cell name + + # Trapezoid 0 + write_uint(buf, 23) # TRAPEZOID record + write_byte(buf, 0b0111_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 50) # height + write_sint(buf, -20) # delta-a + write_sint(buf, 40) # delta-b + write_sint(buf, 0) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # Trapezoid 1 + write_uint(buf, 23) # TRAPEZOID record + write_byte(buf, 0b1010_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 50) # height + write_sint(buf, 20) # delta-a + write_sint(buf, 40) # delta-b + write_sint(buf, 300) # geometry-y (absolute) + + # Trapezoid 2 + write_uint(buf, 23) # TRAPEZOID record + write_byte(buf, 0b1100_1001) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, 20) # delta-a + write_sint(buf, -20) # delta-b + write_sint(buf, 300) # geometry-y (relative) + + # Trapezoid 3 + write_uint(buf, 23) # TRAPEZOID record + write_byte(buf, 0b0100_1101) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, 20) # delta-a + write_sint(buf, -20) # delta-b + write_sint(buf, 300) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 15) # XYABSOLUTE record + + # Trapezoid 4 + write_uint(buf, 24) # TRAPEZOID record + write_byte(buf, 0b0111_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 50) # height + write_sint(buf, -20) # delta-a + write_sint(buf, 1000) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # Trapezoid 5 + write_uint(buf, 24) # TRAPEZOID record + write_byte(buf, 0b1010_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 50) # height + write_sint(buf, 20) # delta-a + write_sint(buf, 300) # geometry-y (relative) + + # Trapezoid 6 + write_uint(buf, 24) # TRAPEZOID record + write_byte(buf, 0b1100_1001) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, 20) # delta-a + write_sint(buf, 300) # geometry-y (relative) + + # Trapezoid 7 + write_uint(buf, 24) # TRAPEZOID record + write_byte(buf, 0b0100_1101) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, 20) # delta-a + write_sint(buf, 300) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + write_uint(buf, 15) # XYABSOLUTE record + + # Trapezoid 8 + write_uint(buf, 25) # TRAPEZOID record + write_byte(buf, 0b0111_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 100) # width + write_uint(buf, 50) # height + write_sint(buf, 40) # delta-b + write_sint(buf, 2000) # geometry-x (absolute) + write_sint(buf, 100) # geometry-y (absolute) + + write_uint(buf, 16) # XYRELATIVE record + + # Trapezoid 9 + write_uint(buf, 25) # TRAPEZOID record + write_byte(buf, 0b1010_1011) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 2) # datatype + write_uint(buf, 50) # height + write_sint(buf, 40) # delta-b + write_sint(buf, 300) # geometry-y (relative) + + # Trapezoid 10 + write_uint(buf, 25) # TRAPEZOID record + write_byte(buf, 0b1100_1001) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, -20) # delta-b + write_sint(buf, 300) # geometry-y (relative) + + # Trapezoid 11 + write_uint(buf, 25) # TRAPEZOID record + write_byte(buf, 0b0100_1101) # OWHX_YRDL + write_uint(buf, 1) # layer + write_uint(buf, 150) # width + write_sint(buf, -20) # delta-b + write_sint(buf, 300) # geometry-y (relative) + write_uint(buf, 1) # repetition (3x4 matrix) + write_uint(buf, 1) # (repetition) x-dimension + write_uint(buf, 2) # (repetition) y-dimension + write_uint(buf, 200) # (repetition) x-spacing + write_uint(buf, 300) # (repetition) y-spacing + + buf.write(FOOTER) + return buf + + +def test_file_1() -> None: + buf = write_file_1(BytesIO()) + + buf.seek(0) + layout = OasisLayout.read(buf) + + base_tests(layout) + + geometry = layout.cells[0].geometry + assert len(geometry) == 12 + + for ii, gg in enumerate(geometry): + msg = f'Failed on trapezoid {ii}' + assert gg.x == 1000 * (ii // 4), msg + assert gg.y == 100 + 300 * (ii % 4), msg + + assert gg.layer == 1, msg + assert gg.datatype == 2, msg + + if ii % 4 == 3: + assert gg.repetition.a_count == 3, msg + assert gg.repetition.b_count == 4, msg + assert gg.repetition.a_vector == [200, 0], msg + assert gg.repetition.b_vector == [0, 300], msg + else: + assert gg.repetition is None, msg + assert not gg.properties, msg + + assert gg.height == 50, msg + if ii % 4 < 2: + assert gg.width == 100, msg + else: + assert gg.width == 150, msg + + if ii in (0, 4): + assert gg.delta_a == -20, msg + elif 8 <= ii: + assert gg.delta_a == 0, msg + else: + assert gg.delta_a == 20, msg + + if ii in (0, 1, 8, 9): + assert gg.delta_b == 40, msg + elif 4 <= ii < 8: + assert gg.delta_b == 0, msg + else: + assert gg.delta_b == -20, msg + + assert gg.is_vertical == ((ii % 4) in (1, 2)), msg + + assert not gg.properties diff --git a/fatamorgana/test/test_int.py b/fatamorgana/test/test_int.py new file mode 100644 index 0000000..5f44053 --- /dev/null +++ b/fatamorgana/test/test_int.py @@ -0,0 +1,71 @@ +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO + +import pytest # type: ignore + +from ..basic import read_uint, read_sint, write_uint, write_sint + + +uints = ( + ( 0, '00'), + ( 127, '7f'), + ( 128, '80 01'), + (16_383, 'ff 7f'), + (16_384, '80 80 01'), + ) + +uints_readonly = ( + ( 0, '80 80 00'), + ) + +sints = ( + ( 0, '00'), + ( 1, '02'), + ( -1, '03'), + ( 63, '7e'), + ( -64, '81 01'), + ( 8191, 'fe 7f'), + ( -8192, '81 80 01'), + ) + +sints_readonly = ( + ) + + +def test_read_uint() -> None: + buffer = BytesIO(bytes.fromhex( + ''.join([hh for _ii, hh in chain(uints, uints_readonly)]))) + + for ii, _hh in chain(uints, uints_readonly): + assert read_uint(buffer) == ii + + +def test_write_uint() -> None: + buffer = BytesIO() + for ii, _hh in uints: + write_uint(buffer, ii) + + correct_bytes = bytes.fromhex( + ''.join([hh for _ii, hh in uints])) + + assert buffer.getbuffer() == correct_bytes + + +def test_read_sint() -> None: + buffer = BytesIO(bytes.fromhex( + ''.join([hh for _ii, hh in chain(sints, sints_readonly)]))) + + for ii, _hh in chain(sints, sints_readonly): + assert read_sint(buffer) == ii + + +def test_write_sint() -> None: + buffer = BytesIO() + for ii, _hh in sints: + write_sint(buffer, ii) + + correct_bytes = bytes.fromhex( + ''.join([hh for _ii, hh in sints])) + + assert buffer.getbuffer() == correct_bytes diff --git a/fatamorgana/test/utils.py b/fatamorgana/test/utils.py new file mode 100644 index 0000000..5407ada --- /dev/null +++ b/fatamorgana/test/utils.py @@ -0,0 +1,41 @@ +from typing import List, Tuple, Iterable +from itertools import chain +from io import BytesIO, BufferedIOBase +import struct + +import pytest # type: ignore + +from ..basic import write_uint, write_sint, read_uint, read_sint, write_bstring +from ..main import OasisLayout + + +MAGIC_BYTES = b'%SEMI-OASIS\r\n' + + +def _gen_header() -> bytes: + buf = BytesIO() + buf.write(MAGIC_BYTES) + + write_uint(buf, 1) # START record + write_bstring(buf, b'1.0') # version + write_uint(buf, 0) # dbu real type: uint + write_uint(buf, 1000) # dbu value: 1000 per micron + write_uint(buf, 0) # offset table is present here + for _ in range(6): + write_uint(buf, 0) # offset table (0: not strict) + write_uint(buf, 0) # offset table (0: no entry present) + return buf.getvalue() + + +def _gen_footer() -> bytes: + buf = BytesIO() + + write_uint(buf, 2) # END record + write_bstring(buf, b'\0' * 252) # padding (1 + 1 + (2 + 252)) = 256 + write_uint(buf, 0) # no validation + return buf.getvalue() + + +HEADER = _gen_header() +FOOTER = _gen_footer() +