From ddf233983772e866984af2b3e2d7ce5a29103240 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 28 Oct 2023 13:32:49 -0400 Subject: [PATCH 01/15] Add dummy xml files to test against - Fixed some default assumptions based on standard - Improved re-draw on new file load, but still some bugs --- .gitignore | 1 + tests/test_wafermap.py | 16 ++++++ tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml | 70 ++++++++++++++++++++++++ tests/xml/SEMI_G85/SEMI_G85_1101_MIN.xml | 70 ++++++++++++++++++++++++ waferview/gui/constants.py | 1 + waferview/gui/gui.py | 16 ++++-- waferview/wafermap.py | 37 ++++++++++--- 7 files changed, 197 insertions(+), 14 deletions(-) create mode 100644 tests/test_wafermap.py create mode 100644 tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml create mode 100644 tests/xml/SEMI_G85/SEMI_G85_1101_MIN.xml diff --git a/.gitignore b/.gitignore index 19016f1..3e8f346 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +.DS_Store # PyInstaller # Usually these files are written by a python script from a template diff --git a/tests/test_wafermap.py b/tests/test_wafermap.py new file mode 100644 index 0000000..da6df91 --- /dev/null +++ b/tests/test_wafermap.py @@ -0,0 +1,16 @@ +"""Tests for wafermap module.""" + +from unittest import mock +from waferview import wafermap + + +class TestWaferMap(unittest.TestCase): + """Test the WaferMap class.""" + + def setUp(self): + """Set up a WaferMap instance.""" + pass + + def tearDown(self): + """Tear down a WaferMap instance.""" + pass diff --git a/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml b/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml new file mode 100644 index 0000000..c23d07a --- /dev/null +++ b/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/xml/SEMI_G85/SEMI_G85_1101_MIN.xml b/tests/xml/SEMI_G85/SEMI_G85_1101_MIN.xml new file mode 100644 index 0000000..a87a765 --- /dev/null +++ b/tests/xml/SEMI_G85/SEMI_G85_1101_MIN.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/waferview/gui/constants.py b/waferview/gui/constants.py index 79f1af8..ec42ed0 100644 --- a/waferview/gui/constants.py +++ b/waferview/gui/constants.py @@ -15,6 +15,7 @@ # Wafer data keys WAFER_ID = "wafer_id" LOT_ID = "lot_id" +WAFER_SIZE = "wafer_size" CHIP_SIZE = "chip_size" PRODUCT_ID = "product_id" CREATE_DATE = "created" diff --git a/waferview/gui/gui.py b/waferview/gui/gui.py index 99e3897..676b2b0 100644 --- a/waferview/gui/gui.py +++ b/waferview/gui/gui.py @@ -26,7 +26,7 @@ def initialize(self): self.create_panels() self.create_menu() self.create_status(self.sizers["grid"]) - self.create_viewer(self.sizers["right"]) + self.create_viewer() self.left_panel.SetSizer(self.sizers["left"]) self.right_panel.SetSizer(self.sizers["right"]) self.grid_panel.SetSizerAndFit(self.sizers["grid"]) @@ -105,14 +105,19 @@ def create_menu(self): self.menubar = MenuBar(self) self.SetMenuBar(self.menubar) - def create_viewer(self, sizer): + def create_viewer(self): """Create the wafer map viewer.""" - sizer.AddStretchSpacer(1) + try: + self.viewer.Destroy() + except AttributeError: + pass + self.sizers["right"].AddStretchSpacer(1) self.viewer = semimap.Viewer( self, self.right_panel, constants.VIEWER_SIZE[0], constants.VIEWER_SIZE[1] ) - sizer.Add(self.viewer, 0, wx.ALIGN_CENTER) - sizer.AddStretchSpacer(1) + self.sizers["right"].Add(self.viewer, 0, wx.ALIGN_CENTER) + self.sizers["right"].AddStretchSpacer(1) + def create_status(self, sizer): """Create the data grid table.""" @@ -156,4 +161,5 @@ def file_browser(self, event): self.file_name = file_dialog.GetPath() # Once a file is selected, we need to generate the map and viewer + self.parent.create_viewer() self.parent.viewer.generate_map(self.file_name) diff --git a/waferview/wafermap.py b/waferview/wafermap.py index 690108e..5ab4c84 100644 --- a/waferview/wafermap.py +++ b/waferview/wafermap.py @@ -7,6 +7,7 @@ SUPPORTED_FORMATS, WAFER_ID, LOT_ID, + WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE, @@ -49,24 +50,27 @@ def get_attributes(self): self.device_attr = { WAFER_ID: self._map_data.get("@WaferId", None), LOT_ID: device_data.get("@LotId", None), + WAFER_SIZE: device_data.get("@WaferSize", 300), CHIP_SIZE: [ - float(device_data.get("@DeviceSizeX", None)), - float(device_data.get("@DeviceSizeY", None)), + float(device_data.get("@DeviceSizeX", 0)), + float(device_data.get("@DeviceSizeY", 0)), ], PRODUCT_ID: device_data.get("@ProductId", None), CREATE_DATE: device_data["Data"].get("@CreateDate"), - "rows": int(device_data.get("@Rows", None)), - "cols": int(device_data.get("@Columns", None)), + "rows": int(device_data.get("@Rows", 1)), + "cols": int(device_data.get("@Columns", 1)), } def get_codes(self): """Get all bin codes.""" bins = self._map_data["Device"]["Bin"] + null_bin = self._map_data["Device"]["@NullBin"] self.bin_codes = {} + bin_incr = 0 for code in bins: code_val = code["@BinCode"] code_pass = code["@BinQuality"] == "Pass" - code_desc = code["@BinDescription"] + code_desc = code.get("@BinDescription", str(bin_incr)) code_count = code["@BinCount"] if code["@BinQuality"] == "NULL": code_pass = None @@ -75,12 +79,22 @@ def get_codes(self): "desc": code_desc, "count": code_count, } + bin_incr += 1 + + if null_bin not in self.bin_codes: + self.bin_codes[null_bin] = { + "status": None, + "desc": "NULL", + "count": None, + } def gen_map(self): """Generate a wafer map with data and coordinates.""" - if self.device_attr[CHIP_SIZE] is None: - self.semimap = None - return + if self.device_attr[CHIP_SIZE] == [0, 0]: + # Need to use rows and columns to guess size + self.device_attr[CHIP_SIZE][0] = 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["cols"] + self.device_attr[CHIP_SIZE][1] = 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["rows"] + # Normalize locations to a 0 to 1 grid with 0,0 at bottom left and # 1,1, at top right xmax = self.device_attr[CHIP_SIZE][0] * self.device_attr["cols"] @@ -93,9 +107,14 @@ def gen_map(self): xloc = 0 yloc = 1 + bin_type = self._map_data["Device"]["@BinType"] + find_str = ".." + if bin_type == "Decimal": + find_str = "..." + for row in self._map_data["Device"]["Data"]["Row"]: # Split data into one byte chunks - row_bins = re.findall("..", row) + row_bins = re.findall(find_str, row) for data in row_bins: status = self.bin_codes[data]["status"] desc = self.bin_codes[data]["desc"] From 3c543a1f7bfb67da8f1df10f21bf4dde9bccb47b Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sat, 28 Oct 2023 23:08:50 -0400 Subject: [PATCH 02/15] Add initial tests --- :q | 252 +++++++++++++++++++++++ tests/test_wafermap.py | 238 ++++++++++++++++++++- tests/test_wafermap_data.py | 63 ++++++ tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml | 6 +- waferview/gui/constants.py | 3 + waferview/wafermap.py | 6 +- 6 files changed, 561 insertions(+), 7 deletions(-) create mode 100644 :q create mode 100644 tests/test_wafermap_data.py diff --git a/:q b/:q new file mode 100644 index 0000000..be6fb5f --- /dev/null +++ b/:q @@ -0,0 +1,252 @@ +"""Tests for wafermap module.""" + +import unittest +from unittest import mock +from waferview import wafermap +from waferview.gui.constants import SUPPORTED_FORMATS, WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE, DEFAULT_WAFER_SIZE + + +TEST_XML = "./xml/SEMI_G85/SEMI_G85_1101_MIN.xml" + +class TestWaferMap(unittest.TestCase): + """Test the WaferMap class.""" + + def setUp(self): + """Set up a WaferMap instance.""" + self.test_wmap = MockWaferMap() + + def tearDown(self): + """Tear down a WaferMap instance.""" + self.test_wmap = None + + def test_check_format_ok(self): + """Test the check_format method.""" + self.test_wmap._map_data = {"@FormatRevision": SUPPORTED_FORMATS[0]} + self.test_wmap.check_format() + self.assertEqual(self.test_wmap.format, SUPPORTED_FORMATS[0]) + self.assertTrue(self.test_wmap.is_valid) + + def test_check_format_ng(self): + """Test the check_format method with bad versioning.""" + self.test_wmap._map_data = {"@FormatRevision": "foobar"} + self.test_wmap.check_format() + self.assertFalse(self.test_wmap.is_valid) + + def test_check_get_attributes_min(self): + """Test the get_attributes method.""" + self.test_wmap._map_data = {"Device": {"@Rows": "13", "@Columns": "17"}} + self.test_wmap.get_attributes() + self.assertEqual(self.test_wmap.device_attr[WAFER_ID], None) + self.assertEqual(self.test_wmap.device_attr[LOT_ID], None) + self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], DEFAULT_WAFER_SIZE) + self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [0, 0]) + self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], None) + self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], None) + self.assertEqual(self.test_wmap.device_attr["rows"], 13) + self.assertEqual(self.test_wmap.device_attr["cols"], 17) + + def test_check_get_attributes_all(self): + """Test the get_attributes method with everything extracted.""" + self.test_wmap._map_data = { + "@WaferId": "foo", + "Device": { + "@LotId": "bar", + "@WaferSize": "100", + "@DeviceSizeX": "42", + "@DeviceSizeY": "24", + "@ProductId": "deadbeef", + "@Rows": "13", + "@Columns": "17", + "@CreateDate": "19700101", + } + } + self.test_wmap.get_attributes() + self.assertEqual(self.test_wmap.device_attr[WAFER_ID], "foo") + self.assertEqual(self.test_wmap.device_attr[LOT_ID], "bar") + self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], 100) + self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [42, 24]) + self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], "deadbeef") + self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], "19700101") + self.assertEqual(self.test_wmap.device_attr["rows"], 13) + self.assertEqual(self.test_wmap.device_attr["cols"], 17) + + def test_get_codes_min(self): + """Test the get_codes method with minimal input.""" + self.test_wmap._map_data = { + "Device": { + "@NullBin": "255", + "Bin": [ + {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100"}, + {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10"}, + {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1"}, + ] + } + } + + self.test_wmap.get_codes() + + self.assertTrue("000" in self.test_wmap.bin_codes) + self.assertTrue("111" in self.test_wmap.bin_codes) + self.assertTrue("222" in self.test_wmap.bin_codes) + self.assertTrue("255" in self.test_wmap.bin_codes) + + self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) + self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) + + self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "0") + self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "1") + self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "2") + self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "NULL") + + self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") + self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") + self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") + self.assertEqual(self.test_wmap.bin_codes["255"]["count"], None) + + def test_get_codes_all(self): + """Test the get_codes method with all input.""" + self.test_wmap._map_data = { + "Device": { + "@NullBin": "255", + "Bin": [ + {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100", "@BinDescription": "foo"}, + {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10", "@BinDescription": "bar"}, + {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1", "@BinDescription": "beef"}, + {"@BinCode": "255", "@BinQuality": "NULL", "@BinCount": "0", "@BinDescription": "Null bin"} + ] + } + } + + self.test_wmap.get_codes() + + self.assertTrue("000" in self.test_wmap.bin_codes) + self.assertTrue("111" in self.test_wmap.bin_codes) + self.assertTrue("222" in self.test_wmap.bin_codes) + self.assertTrue("255" in self.test_wmap.bin_codes) + + self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) + self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) + + self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "foo") + self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "bar") + self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "beef") + self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "Null bin") + + self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") + self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") + self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") + self.assertEqual(self.test_wmap.bin_codes["255"]["count"], "0") + + def test_gen_map_dec_bins(self): + """Test the gen_map method.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["000111", "111222"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_dec_bins_min(self): + """Test the gen_map method with no chip size.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [0, 0] + self.test_wmap.device_attr[WAFER_SIZE] = 0.002 + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["000111", "111222"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_hex_bins(self): + """Test the gen_map method.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "00": {"status": True, "desc": None, "count": "1"}, + "11": {"status": False, "desc": None, "count": "2"}, + "22": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "HexaDecimal", + "Data": { + "Row": ["0011", "1122"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_bad_units(self): + """Test the gen_map method with incorrectly spec'd bins.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["0011", "1122"], + } + } + } + + with self.assertRaises(KeyError): + self.test_wmap.gen_map() + + +class MockWaferMap(wafermap.WaferMap): + """Mock of a wafermap class.""" + + def __init__(self): + """Override init of wafermap.""" + pass diff --git a/tests/test_wafermap.py b/tests/test_wafermap.py index da6df91..be6fb5f 100644 --- a/tests/test_wafermap.py +++ b/tests/test_wafermap.py @@ -1,16 +1,252 @@ """Tests for wafermap module.""" +import unittest from unittest import mock from waferview import wafermap +from waferview.gui.constants import SUPPORTED_FORMATS, WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE, DEFAULT_WAFER_SIZE +TEST_XML = "./xml/SEMI_G85/SEMI_G85_1101_MIN.xml" + class TestWaferMap(unittest.TestCase): """Test the WaferMap class.""" def setUp(self): """Set up a WaferMap instance.""" - pass + self.test_wmap = MockWaferMap() def tearDown(self): """Tear down a WaferMap instance.""" + self.test_wmap = None + + def test_check_format_ok(self): + """Test the check_format method.""" + self.test_wmap._map_data = {"@FormatRevision": SUPPORTED_FORMATS[0]} + self.test_wmap.check_format() + self.assertEqual(self.test_wmap.format, SUPPORTED_FORMATS[0]) + self.assertTrue(self.test_wmap.is_valid) + + def test_check_format_ng(self): + """Test the check_format method with bad versioning.""" + self.test_wmap._map_data = {"@FormatRevision": "foobar"} + self.test_wmap.check_format() + self.assertFalse(self.test_wmap.is_valid) + + def test_check_get_attributes_min(self): + """Test the get_attributes method.""" + self.test_wmap._map_data = {"Device": {"@Rows": "13", "@Columns": "17"}} + self.test_wmap.get_attributes() + self.assertEqual(self.test_wmap.device_attr[WAFER_ID], None) + self.assertEqual(self.test_wmap.device_attr[LOT_ID], None) + self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], DEFAULT_WAFER_SIZE) + self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [0, 0]) + self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], None) + self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], None) + self.assertEqual(self.test_wmap.device_attr["rows"], 13) + self.assertEqual(self.test_wmap.device_attr["cols"], 17) + + def test_check_get_attributes_all(self): + """Test the get_attributes method with everything extracted.""" + self.test_wmap._map_data = { + "@WaferId": "foo", + "Device": { + "@LotId": "bar", + "@WaferSize": "100", + "@DeviceSizeX": "42", + "@DeviceSizeY": "24", + "@ProductId": "deadbeef", + "@Rows": "13", + "@Columns": "17", + "@CreateDate": "19700101", + } + } + self.test_wmap.get_attributes() + self.assertEqual(self.test_wmap.device_attr[WAFER_ID], "foo") + self.assertEqual(self.test_wmap.device_attr[LOT_ID], "bar") + self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], 100) + self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [42, 24]) + self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], "deadbeef") + self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], "19700101") + self.assertEqual(self.test_wmap.device_attr["rows"], 13) + self.assertEqual(self.test_wmap.device_attr["cols"], 17) + + def test_get_codes_min(self): + """Test the get_codes method with minimal input.""" + self.test_wmap._map_data = { + "Device": { + "@NullBin": "255", + "Bin": [ + {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100"}, + {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10"}, + {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1"}, + ] + } + } + + self.test_wmap.get_codes() + + self.assertTrue("000" in self.test_wmap.bin_codes) + self.assertTrue("111" in self.test_wmap.bin_codes) + self.assertTrue("222" in self.test_wmap.bin_codes) + self.assertTrue("255" in self.test_wmap.bin_codes) + + self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) + self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) + + self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "0") + self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "1") + self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "2") + self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "NULL") + + self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") + self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") + self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") + self.assertEqual(self.test_wmap.bin_codes["255"]["count"], None) + + def test_get_codes_all(self): + """Test the get_codes method with all input.""" + self.test_wmap._map_data = { + "Device": { + "@NullBin": "255", + "Bin": [ + {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100", "@BinDescription": "foo"}, + {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10", "@BinDescription": "bar"}, + {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1", "@BinDescription": "beef"}, + {"@BinCode": "255", "@BinQuality": "NULL", "@BinCount": "0", "@BinDescription": "Null bin"} + ] + } + } + + self.test_wmap.get_codes() + + self.assertTrue("000" in self.test_wmap.bin_codes) + self.assertTrue("111" in self.test_wmap.bin_codes) + self.assertTrue("222" in self.test_wmap.bin_codes) + self.assertTrue("255" in self.test_wmap.bin_codes) + + self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) + self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) + self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) + + self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "foo") + self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "bar") + self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "beef") + self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "Null bin") + + self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") + self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") + self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") + self.assertEqual(self.test_wmap.bin_codes["255"]["count"], "0") + + def test_gen_map_dec_bins(self): + """Test the gen_map method.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["000111", "111222"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_dec_bins_min(self): + """Test the gen_map method with no chip size.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [0, 0] + self.test_wmap.device_attr[WAFER_SIZE] = 0.002 + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["000111", "111222"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_hex_bins(self): + """Test the gen_map method.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "00": {"status": True, "desc": None, "count": "1"}, + "11": {"status": False, "desc": None, "count": "2"}, + "22": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "HexaDecimal", + "Data": { + "Row": ["0011", "1122"], + } + } + } + + self.test_wmap.gen_map() + self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) + self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) + + def test_gen_map_bad_units(self): + """Test the gen_map method with incorrectly spec'd bins.""" + self.test_wmap.device_attr = {} + self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] + self.test_wmap.device_attr["rows"] = 2 + self.test_wmap.device_attr["cols"] = 2 + self.test_wmap.bin_codes = { + "000": {"status": True, "desc": None, "count": "1"}, + "111": {"status": False, "desc": None, "count": "2"}, + "222": {"status": None, "desc": "NULL", "count": "3"}, + } + self.test_wmap._map_data = { + "Device": { + "@BinType": "Decimal", + "Data": { + "Row": ["0011", "1122"], + } + } + } + + with self.assertRaises(KeyError): + self.test_wmap.gen_map() + + +class MockWaferMap(wafermap.WaferMap): + """Mock of a wafermap class.""" + + def __init__(self): + """Override init of wafermap.""" pass diff --git a/tests/test_wafermap_data.py b/tests/test_wafermap_data.py new file mode 100644 index 0000000..a4a0f4e --- /dev/null +++ b/tests/test_wafermap_data.py @@ -0,0 +1,63 @@ +"""Test wafermap with dummy data.""" + +import os +import unittest +from unittest import mock +from waferview import wafermap +from waferview.gui.constants import WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE + + +TEST_PATH = os.path.split(os.path.abspath(__file__))[0] + +class TestWaferMapData(unittest.TestCase): + """Test the WaferMap class on real XML data.""" + + def test_semi_g85_1101_minimal(self): + """Test G85-1101 minimal standard.""" + TEST_XML = os.path.join(TEST_PATH, "xml/SEMI_G85/SEMI_G85_1101_MIN.xml") + test_wmap = wafermap.WaferMap(TEST_XML) + self.assertTrue("00" in test_wmap.bin_codes) + self.assertTrue("DE" in test_wmap.bin_codes) + self.assertTrue("AD" in test_wmap.bin_codes) + self.assertTrue("FF" in test_wmap.bin_codes) + self.assertEqual(test_wmap.device_attr["rows"], 60) + self.assertEqual(test_wmap.device_attr["cols"], 60) + self.assertTrue(test_wmap.bin_codes["00"]["status"]) + self.assertFalse(test_wmap.bin_codes["DE"]["status"]) + self.assertFalse(test_wmap.bin_codes["AD"]["status"]) + self.assertEqual(test_wmap.bin_codes["FF"]["status"], None) + self.assertEqual(test_wmap.bin_codes["00"]["count"], "2765") + self.assertEqual(test_wmap.bin_codes["DE"]["count"], "38") + self.assertEqual(test_wmap.bin_codes["AD"]["count"], "5") + self.assertEqual(len(test_wmap.pixels), 3600) + + def test_semi_g85_1101_full(self): + """Test G85-1101 full standard.""" + TEST_XML = os.path.join(TEST_PATH, "xml/SEMI_G85/SEMI_G85_1101_ALL.xml") + test_wmap = wafermap.WaferMap(TEST_XML) + self.assertTrue("00" in test_wmap.bin_codes) + self.assertTrue("DE" in test_wmap.bin_codes) + self.assertTrue("AD" in test_wmap.bin_codes) + self.assertTrue("FF" in test_wmap.bin_codes) + self.assertEqual(test_wmap.device_attr["rows"], 60) + self.assertEqual(test_wmap.device_attr["cols"], 60) + self.assertTrue(test_wmap.bin_codes["00"]["status"]) + self.assertFalse(test_wmap.bin_codes["DE"]["status"]) + self.assertFalse(test_wmap.bin_codes["AD"]["status"]) + self.assertEqual(test_wmap.bin_codes["FF"]["status"], None) + self.assertEqual(test_wmap.bin_codes["00"]["count"], "2765") + self.assertEqual(test_wmap.bin_codes["DE"]["count"], "38") + self.assertEqual(test_wmap.bin_codes["AD"]["count"], "5") + self.assertEqual(len(test_wmap.pixels), 3600) + + exp_attr = { + WAFER_ID: "ABCD123", + LOT_ID: "DEADBEEF", + WAFER_SIZE: 300, + CHIP_SIZE: [5000,5000], + PRODUCT_ID: "FOOBAR", + CREATE_DATE: "20231028123456", + "rows": 60, + "cols": 60, + } + self.assertDictEqual(test_wmap.device_attr, exp_attr) diff --git a/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml b/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml index c23d07a..14ae6bb 100644 --- a/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml +++ b/tests/xml/SEMI_G85/SEMI_G85_1101_ALL.xml @@ -1,10 +1,10 @@ - - + + - + diff --git a/waferview/gui/constants.py b/waferview/gui/constants.py index ec42ed0..76f21c3 100644 --- a/waferview/gui/constants.py +++ b/waferview/gui/constants.py @@ -20,3 +20,6 @@ PRODUCT_ID = "product_id" CREATE_DATE = "created" ALL_KEYS = [WAFER_ID, LOT_ID, CHIP_SIZE, PRODUCT_ID, CREATE_DATE] + +# Various constants +DEFAULT_WAFER_SIZE = 300 diff --git a/waferview/wafermap.py b/waferview/wafermap.py index 5ab4c84..ae1047c 100644 --- a/waferview/wafermap.py +++ b/waferview/wafermap.py @@ -11,6 +11,7 @@ CHIP_SIZE, PRODUCT_ID, CREATE_DATE, + DEFAULT_WAFER_SIZE ) @@ -31,7 +32,6 @@ def parse(self, xmlfile): # Strip namespace from XML file for _, elem in tree: _, _, elem.tag = elem.tag.rpartition("}") - # tree = ET.parse(xmlfile) xml_data = tree.root xmlstr = ET.tostring(xml_data, encoding="utf-8", method="xml") xml_dict = xmltodict.parse(xmlstr) @@ -50,13 +50,13 @@ def get_attributes(self): self.device_attr = { WAFER_ID: self._map_data.get("@WaferId", None), LOT_ID: device_data.get("@LotId", None), - WAFER_SIZE: device_data.get("@WaferSize", 300), + WAFER_SIZE: float(device_data.get("@WaferSize", DEFAULT_WAFER_SIZE)), CHIP_SIZE: [ float(device_data.get("@DeviceSizeX", 0)), float(device_data.get("@DeviceSizeY", 0)), ], PRODUCT_ID: device_data.get("@ProductId", None), - CREATE_DATE: device_data["Data"].get("@CreateDate"), + CREATE_DATE: device_data.get("@CreateDate", None), "rows": int(device_data.get("@Rows", 1)), "cols": int(device_data.get("@Columns", 1)), } From 711d09be6b74c267a44a68ebc0163e05f136b134 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 21:56:43 -0400 Subject: [PATCH 03/15] Fix lint errors. Add mac and windows to workflow --- .github/workflows/test.yml | 22 +++++++++------ tests/test_wafermap.py | 55 ++++++++++++++++++++++++++++--------- tests/test_wafermap_data.py | 13 +++++++-- waferview/gui/gui.py | 1 - waferview/wafermap.py | 10 +++++-- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e49b98..95ae68c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,28 +8,34 @@ on: jobs: build: - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.os }} strategy: max-parallel: 4 matrix: - platform: - - ubuntu-22.04 - python-version: ['3.9', '3.10', '3.11', '3.12'] - + os: [ubuntu-20.04, ubuntu-22.04, windows-latest, macos-latest] + python-version: '3.10' + include: + - os: ubuntu-20.04 + download_wheel: 1 + - os: ubuntu-22.04 + download_wheel: 1 + python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Install GTK + - if: ${{ matrix.download_wheel }} + name: Install GTK run: | sudo apt-get update sudo apt-get install build-essential libgtk-3-dev - - name: Install wxPython + - if: ${{ matrix.download_wheel }} + name: Install wxPython run: | python -m pip install --upgrade pip - pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-22.04 wxPython --user + pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }} wxPython --user - name: Install Dependencies run: | pip install -r requirements.txt diff --git a/tests/test_wafermap.py b/tests/test_wafermap.py index be6fb5f..f9b85ad 100644 --- a/tests/test_wafermap.py +++ b/tests/test_wafermap.py @@ -1,13 +1,22 @@ """Tests for wafermap module.""" import unittest -from unittest import mock from waferview import wafermap -from waferview.gui.constants import SUPPORTED_FORMATS, WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE, DEFAULT_WAFER_SIZE +from waferview.gui.constants import ( + SUPPORTED_FORMATS, + WAFER_ID, + LOT_ID, + WAFER_SIZE, + CHIP_SIZE, + PRODUCT_ID, + CREATE_DATE, + DEFAULT_WAFER_SIZE, +) TEST_XML = "./xml/SEMI_G85/SEMI_G85_1101_MIN.xml" + class TestWaferMap(unittest.TestCase): """Test the WaferMap class.""" @@ -58,7 +67,7 @@ def test_check_get_attributes_all(self): "@Rows": "13", "@Columns": "17", "@CreateDate": "19700101", - } + }, } self.test_wmap.get_attributes() self.assertEqual(self.test_wmap.device_attr[WAFER_ID], "foo") @@ -79,7 +88,7 @@ def test_get_codes_min(self): {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100"}, {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10"}, {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1"}, - ] + ], } } @@ -111,11 +120,31 @@ def test_get_codes_all(self): "Device": { "@NullBin": "255", "Bin": [ - {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100", "@BinDescription": "foo"}, - {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10", "@BinDescription": "bar"}, - {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1", "@BinDescription": "beef"}, - {"@BinCode": "255", "@BinQuality": "NULL", "@BinCount": "0", "@BinDescription": "Null bin"} - ] + { + "@BinCode": "000", + "@BinQuality": "Pass", + "@BinCount": "100", + "@BinDescription": "foo", + }, + { + "@BinCode": "111", + "@BinQuality": "Fail", + "@BinCount": "10", + "@BinDescription": "bar", + }, + { + "@BinCode": "222", + "@BinQuality": "Fail", + "@BinCount": "1", + "@BinDescription": "beef", + }, + { + "@BinCode": "255", + "@BinQuality": "NULL", + "@BinCount": "0", + "@BinDescription": "Null bin", + }, + ], } } @@ -157,7 +186,7 @@ def test_gen_map_dec_bins(self): "@BinType": "Decimal", "Data": { "Row": ["000111", "111222"], - } + }, } } @@ -184,7 +213,7 @@ def test_gen_map_dec_bins_min(self): "@BinType": "Decimal", "Data": { "Row": ["000111", "111222"], - } + }, } } @@ -210,7 +239,7 @@ def test_gen_map_hex_bins(self): "@BinType": "HexaDecimal", "Data": { "Row": ["0011", "1122"], - } + }, } } @@ -236,7 +265,7 @@ def test_gen_map_bad_units(self): "@BinType": "Decimal", "Data": { "Row": ["0011", "1122"], - } + }, } } diff --git a/tests/test_wafermap_data.py b/tests/test_wafermap_data.py index a4a0f4e..3c71217 100644 --- a/tests/test_wafermap_data.py +++ b/tests/test_wafermap_data.py @@ -2,13 +2,20 @@ import os import unittest -from unittest import mock from waferview import wafermap -from waferview.gui.constants import WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE +from waferview.gui.constants import ( + WAFER_ID, + LOT_ID, + WAFER_SIZE, + CHIP_SIZE, + PRODUCT_ID, + CREATE_DATE, +) TEST_PATH = os.path.split(os.path.abspath(__file__))[0] + class TestWaferMapData(unittest.TestCase): """Test the WaferMap class on real XML data.""" @@ -54,7 +61,7 @@ def test_semi_g85_1101_full(self): WAFER_ID: "ABCD123", LOT_ID: "DEADBEEF", WAFER_SIZE: 300, - CHIP_SIZE: [5000,5000], + CHIP_SIZE: [5000, 5000], PRODUCT_ID: "FOOBAR", CREATE_DATE: "20231028123456", "rows": 60, diff --git a/waferview/gui/gui.py b/waferview/gui/gui.py index 676b2b0..0f52e60 100644 --- a/waferview/gui/gui.py +++ b/waferview/gui/gui.py @@ -118,7 +118,6 @@ def create_viewer(self): self.sizers["right"].Add(self.viewer, 0, wx.ALIGN_CENTER) self.sizers["right"].AddStretchSpacer(1) - def create_status(self, sizer): """Create the data grid table.""" self.data_grid = semimap.DataGrid(self.grid_panel) diff --git a/waferview/wafermap.py b/waferview/wafermap.py index ae1047c..0f90945 100644 --- a/waferview/wafermap.py +++ b/waferview/wafermap.py @@ -11,7 +11,7 @@ CHIP_SIZE, PRODUCT_ID, CREATE_DATE, - DEFAULT_WAFER_SIZE + DEFAULT_WAFER_SIZE, ) @@ -92,8 +92,12 @@ def gen_map(self): """Generate a wafer map with data and coordinates.""" if self.device_attr[CHIP_SIZE] == [0, 0]: # Need to use rows and columns to guess size - self.device_attr[CHIP_SIZE][0] = 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["cols"] - self.device_attr[CHIP_SIZE][1] = 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["rows"] + self.device_attr[CHIP_SIZE][0] = ( + 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["cols"] + ) + self.device_attr[CHIP_SIZE][1] = ( + 1000 * self.device_attr[WAFER_SIZE] / self.device_attr["rows"] + ) # Normalize locations to a 0 to 1 grid with 0,0 at bottom left and # 1,1, at top right From 205e444120a6f3c1a7dc3869456facbb4e06f4a4 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 21:59:55 -0400 Subject: [PATCH 04/15] Fix python version in workflow. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95ae68c..9181164 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-20.04, ubuntu-22.04, windows-latest, macos-latest] - python-version: '3.10' + python-version: ['3.10'] include: - os: ubuntu-20.04 download_wheel: 1 From 31e39bd2277bc636e6b2b0b499b6d63777950ac9 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:06:15 -0400 Subject: [PATCH 05/15] Fix conditionals in workflow. --- .github/workflows/test.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9181164..ef2965e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,10 +15,7 @@ jobs: os: [ubuntu-20.04, ubuntu-22.04, windows-latest, macos-latest] python-version: ['3.10'] include: - - os: ubuntu-20.04 - download_wheel: 1 - os: ubuntu-22.04 - download_wheel: 1 python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 @@ -26,16 +23,20 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - if: ${{ matrix.download_wheel }} - name: Install GTK + - name: Update Pip run: | - sudo apt-get update - sudo apt-get install build-essential libgtk-3-dev - - if: ${{ matrix.download_wheel }} - name: Install wxPython + python -m pip install --upgrade pip + - name: Install GTK run: | - python -m pip install --upgrade pip - pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }} wxPython --user + if [ "$RUNNER_OS" == "Linux" ]; then + sudo apt-get update + sudo apt-get install build-essential libgtk-3-dev + fi + - name: Install wxPython + run: | + if [ "$RUNNER_OS" == "Linux"]; then + pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }} wxPython --user + fi - name: Install Dependencies run: | pip install -r requirements.txt From 28be4253163203020f683b573361dfb7f95c64bd Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:09:02 -0400 Subject: [PATCH 06/15] Remove windows from workflow. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef2965e..a0cde0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-20.04, ubuntu-22.04, windows-latest, macos-latest] + os: [ubuntu-20.04, ubuntu-22.04, macos-latest] python-version: ['3.10'] include: - os: ubuntu-22.04 From 531e511d54967d0731346f051bf8c7487d9c1f6d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:17:13 -0400 Subject: [PATCH 07/15] Honestly just guessing on how to fix this now. --- .github/workflows/test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0cde0d..4c32a4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,10 +13,12 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-20.04, ubuntu-22.04, macos-latest] - python-version: ['3.10'] + python-version: ['3.9', '3.10', '3.11'] include: - - os: ubuntu-22.04 - python-version: ['3.9', '3.10', '3.11'] + - os: ubuntu-20.04 + python-version: ['3.10'] + - os: macos-latest + python-version: ['3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From f861a23fd8c9c17ed2fead748ff6ed9831e899b4 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:19:41 -0400 Subject: [PATCH 08/15] Ok maybe I understand now... --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c32a4a..c5738d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,11 +12,11 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-latest] - python-version: ['3.9', '3.10', '3.11'] include: - os: ubuntu-20.04 python-version: ['3.10'] + - os: ubuntu-22.04 + python-version: ['3.9', '3.10', '3.11'] - os: macos-latest python-version: ['3.11'] steps: From 08d5c40eb218eddef13d19f99507d483c3efd01c Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:26:00 -0400 Subject: [PATCH 09/15] Being very explicit now. --- .github/workflows/test.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5738d7..37fb267 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,14 +11,19 @@ jobs: runs-on: ${{ matrix.os }} strategy: max-parallel: 4 + fail-fast: false matrix: include: - - os: ubuntu-20.04 - python-version: ['3.10'] - os: ubuntu-22.04 - python-version: ['3.9', '3.10', '3.11'] + python-version: '3.9' + - os: ubuntu-22.04 + python-version: '3.10' + - os: ubuntu-22.04 + python-version: '3.11' + - os: ubuntu-20.04 + python-version: '3.10' - os: macos-latest - python-version: ['3.11'] + python-version: '3.11' steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 807d0844a35de18d07212e416c64e63cd2cac494 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:31:06 -0400 Subject: [PATCH 10/15] Better conditional --- .github/workflows/test.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37fb267..b3b3394 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,12 +16,16 @@ jobs: include: - os: ubuntu-22.04 python-version: '3.9' + download-wheel: true - os: ubuntu-22.04 python-version: '3.10' + download-wheel: true - os: ubuntu-22.04 python-version: '3.11' + download-wheel: true - os: ubuntu-20.04 python-version: '3.10' + download-wheel: true - os: macos-latest python-version: '3.11' steps: @@ -33,17 +37,15 @@ jobs: - name: Update Pip run: | python -m pip install --upgrade pip - - name: Install GTK + - if: ${{ matrix.download-wheel }} + name: Install GTK run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt-get update - sudo apt-get install build-essential libgtk-3-dev - fi - - name: Install wxPython + sudo apt-get update + sudo apt-get install build-essential libgtk-3-dev + - if: ${{ matrix.download-wheel }} + name: Install wxPython run: | - if [ "$RUNNER_OS" == "Linux"]; then pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }} wxPython --user - fi - name: Install Dependencies run: | pip install -r requirements.txt From 3ded720aa3736d8f0e4ee38433ceffaec6103999 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:34:04 -0400 Subject: [PATCH 11/15] Add windows back into workflow --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3b3394..fce2a10 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,8 @@ jobs: download-wheel: true - os: macos-latest python-version: '3.11' + - os: windows-latest + python-version: '3.11' steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From a1dbc5012d46a418e32127ff6ac17114e76579a9 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:46:07 -0400 Subject: [PATCH 12/15] Try adding lfs flag --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fce2a10..fac209e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,7 @@ jobs: python-version: '3.11' steps: - uses: actions/checkout@v3 + lfs: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From 6c357a9a443188af4e7e441d0fe4b72f0775990f Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:47:27 -0400 Subject: [PATCH 13/15] Fix bad syntax. --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fac209e..e668cfe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,8 @@ jobs: python-version: '3.11' steps: - uses: actions/checkout@v3 - lfs: true + with: + lfs: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From 3efa8a96bda03fda2ccba54d65e6151fba9218a4 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:51:53 -0400 Subject: [PATCH 14/15] WTF was this doing here???? --- :q | 252 ------------------------------------------------------------- 1 file changed, 252 deletions(-) delete mode 100644 :q diff --git a/:q b/:q deleted file mode 100644 index be6fb5f..0000000 --- a/:q +++ /dev/null @@ -1,252 +0,0 @@ -"""Tests for wafermap module.""" - -import unittest -from unittest import mock -from waferview import wafermap -from waferview.gui.constants import SUPPORTED_FORMATS, WAFER_ID, LOT_ID, WAFER_SIZE, CHIP_SIZE, PRODUCT_ID, CREATE_DATE, DEFAULT_WAFER_SIZE - - -TEST_XML = "./xml/SEMI_G85/SEMI_G85_1101_MIN.xml" - -class TestWaferMap(unittest.TestCase): - """Test the WaferMap class.""" - - def setUp(self): - """Set up a WaferMap instance.""" - self.test_wmap = MockWaferMap() - - def tearDown(self): - """Tear down a WaferMap instance.""" - self.test_wmap = None - - def test_check_format_ok(self): - """Test the check_format method.""" - self.test_wmap._map_data = {"@FormatRevision": SUPPORTED_FORMATS[0]} - self.test_wmap.check_format() - self.assertEqual(self.test_wmap.format, SUPPORTED_FORMATS[0]) - self.assertTrue(self.test_wmap.is_valid) - - def test_check_format_ng(self): - """Test the check_format method with bad versioning.""" - self.test_wmap._map_data = {"@FormatRevision": "foobar"} - self.test_wmap.check_format() - self.assertFalse(self.test_wmap.is_valid) - - def test_check_get_attributes_min(self): - """Test the get_attributes method.""" - self.test_wmap._map_data = {"Device": {"@Rows": "13", "@Columns": "17"}} - self.test_wmap.get_attributes() - self.assertEqual(self.test_wmap.device_attr[WAFER_ID], None) - self.assertEqual(self.test_wmap.device_attr[LOT_ID], None) - self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], DEFAULT_WAFER_SIZE) - self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [0, 0]) - self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], None) - self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], None) - self.assertEqual(self.test_wmap.device_attr["rows"], 13) - self.assertEqual(self.test_wmap.device_attr["cols"], 17) - - def test_check_get_attributes_all(self): - """Test the get_attributes method with everything extracted.""" - self.test_wmap._map_data = { - "@WaferId": "foo", - "Device": { - "@LotId": "bar", - "@WaferSize": "100", - "@DeviceSizeX": "42", - "@DeviceSizeY": "24", - "@ProductId": "deadbeef", - "@Rows": "13", - "@Columns": "17", - "@CreateDate": "19700101", - } - } - self.test_wmap.get_attributes() - self.assertEqual(self.test_wmap.device_attr[WAFER_ID], "foo") - self.assertEqual(self.test_wmap.device_attr[LOT_ID], "bar") - self.assertEqual(self.test_wmap.device_attr[WAFER_SIZE], 100) - self.assertEqual(self.test_wmap.device_attr[CHIP_SIZE], [42, 24]) - self.assertEqual(self.test_wmap.device_attr[PRODUCT_ID], "deadbeef") - self.assertEqual(self.test_wmap.device_attr[CREATE_DATE], "19700101") - self.assertEqual(self.test_wmap.device_attr["rows"], 13) - self.assertEqual(self.test_wmap.device_attr["cols"], 17) - - def test_get_codes_min(self): - """Test the get_codes method with minimal input.""" - self.test_wmap._map_data = { - "Device": { - "@NullBin": "255", - "Bin": [ - {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100"}, - {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10"}, - {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1"}, - ] - } - } - - self.test_wmap.get_codes() - - self.assertTrue("000" in self.test_wmap.bin_codes) - self.assertTrue("111" in self.test_wmap.bin_codes) - self.assertTrue("222" in self.test_wmap.bin_codes) - self.assertTrue("255" in self.test_wmap.bin_codes) - - self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) - self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) - self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) - self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) - - self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "0") - self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "1") - self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "2") - self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "NULL") - - self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") - self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") - self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") - self.assertEqual(self.test_wmap.bin_codes["255"]["count"], None) - - def test_get_codes_all(self): - """Test the get_codes method with all input.""" - self.test_wmap._map_data = { - "Device": { - "@NullBin": "255", - "Bin": [ - {"@BinCode": "000", "@BinQuality": "Pass", "@BinCount": "100", "@BinDescription": "foo"}, - {"@BinCode": "111", "@BinQuality": "Fail", "@BinCount": "10", "@BinDescription": "bar"}, - {"@BinCode": "222", "@BinQuality": "Fail", "@BinCount": "1", "@BinDescription": "beef"}, - {"@BinCode": "255", "@BinQuality": "NULL", "@BinCount": "0", "@BinDescription": "Null bin"} - ] - } - } - - self.test_wmap.get_codes() - - self.assertTrue("000" in self.test_wmap.bin_codes) - self.assertTrue("111" in self.test_wmap.bin_codes) - self.assertTrue("222" in self.test_wmap.bin_codes) - self.assertTrue("255" in self.test_wmap.bin_codes) - - self.assertTrue(self.test_wmap.bin_codes["000"]["status"]) - self.assertFalse(self.test_wmap.bin_codes["111"]["status"]) - self.assertFalse(self.test_wmap.bin_codes["222"]["status"]) - self.assertEqual(self.test_wmap.bin_codes["255"]["status"], None) - - self.assertEqual(self.test_wmap.bin_codes["000"]["desc"], "foo") - self.assertEqual(self.test_wmap.bin_codes["111"]["desc"], "bar") - self.assertEqual(self.test_wmap.bin_codes["222"]["desc"], "beef") - self.assertEqual(self.test_wmap.bin_codes["255"]["desc"], "Null bin") - - self.assertEqual(self.test_wmap.bin_codes["000"]["count"], "100") - self.assertEqual(self.test_wmap.bin_codes["111"]["count"], "10") - self.assertEqual(self.test_wmap.bin_codes["222"]["count"], "1") - self.assertEqual(self.test_wmap.bin_codes["255"]["count"], "0") - - def test_gen_map_dec_bins(self): - """Test the gen_map method.""" - self.test_wmap.device_attr = {} - self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] - self.test_wmap.device_attr["rows"] = 2 - self.test_wmap.device_attr["cols"] = 2 - self.test_wmap.bin_codes = { - "000": {"status": True, "desc": None, "count": "1"}, - "111": {"status": False, "desc": None, "count": "2"}, - "222": {"status": None, "desc": "NULL", "count": "3"}, - } - self.test_wmap._map_data = { - "Device": { - "@BinType": "Decimal", - "Data": { - "Row": ["000111", "111222"], - } - } - } - - self.test_wmap.gen_map() - self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) - - def test_gen_map_dec_bins_min(self): - """Test the gen_map method with no chip size.""" - self.test_wmap.device_attr = {} - self.test_wmap.device_attr[CHIP_SIZE] = [0, 0] - self.test_wmap.device_attr[WAFER_SIZE] = 0.002 - self.test_wmap.device_attr["rows"] = 2 - self.test_wmap.device_attr["cols"] = 2 - self.test_wmap.bin_codes = { - "000": {"status": True, "desc": None, "count": "1"}, - "111": {"status": False, "desc": None, "count": "2"}, - "222": {"status": None, "desc": "NULL", "count": "3"}, - } - self.test_wmap._map_data = { - "Device": { - "@BinType": "Decimal", - "Data": { - "Row": ["000111", "111222"], - } - } - } - - self.test_wmap.gen_map() - self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) - - def test_gen_map_hex_bins(self): - """Test the gen_map method.""" - self.test_wmap.device_attr = {} - self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] - self.test_wmap.device_attr["rows"] = 2 - self.test_wmap.device_attr["cols"] = 2 - self.test_wmap.bin_codes = { - "00": {"status": True, "desc": None, "count": "1"}, - "11": {"status": False, "desc": None, "count": "2"}, - "22": {"status": None, "desc": "NULL", "count": "3"}, - } - self.test_wmap._map_data = { - "Device": { - "@BinType": "HexaDecimal", - "Data": { - "Row": ["0011", "1122"], - } - } - } - - self.test_wmap.gen_map() - self.assertTrue([(0, 0.5), (0.5, 0.5), True, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0.5), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0, 0), (0.5, 0.5), False, None] in self.test_wmap.pixels) - self.assertTrue([(0.5, 0), (0.5, 0.5), None, "NULL"] in self.test_wmap.pixels) - - def test_gen_map_bad_units(self): - """Test the gen_map method with incorrectly spec'd bins.""" - self.test_wmap.device_attr = {} - self.test_wmap.device_attr[CHIP_SIZE] = [1, 1] - self.test_wmap.device_attr["rows"] = 2 - self.test_wmap.device_attr["cols"] = 2 - self.test_wmap.bin_codes = { - "000": {"status": True, "desc": None, "count": "1"}, - "111": {"status": False, "desc": None, "count": "2"}, - "222": {"status": None, "desc": "NULL", "count": "3"}, - } - self.test_wmap._map_data = { - "Device": { - "@BinType": "Decimal", - "Data": { - "Row": ["0011", "1122"], - } - } - } - - with self.assertRaises(KeyError): - self.test_wmap.gen_map() - - -class MockWaferMap(wafermap.WaferMap): - """Mock of a wafermap class.""" - - def __init__(self): - """Override init of wafermap.""" - pass From d2a8b64f0c923ec7f1c5168df003b3da8b7f789c Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Sun, 29 Oct 2023 22:55:29 -0400 Subject: [PATCH 15/15] Upgrade checkout to v4 --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0df8d94..6b84269 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: python-version: [3.9] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e668cfe..5eeaddc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: - os: windows-latest python-version: '3.11' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: lfs: true - name: Set up Python ${{ matrix.python-version }}