Coverage for tests/tech_info_test.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-17 19:08 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2026 Martin Jan Köhler and Harald Pretl 

4# Johannes Kepler University, Institute for Integrated Circuits. 

5# 

6# This file is part of KPEX  

7# (see https://github.com/iic-jku/klayout-pex). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21# SPDX-License-Identifier: GPL-3.0-or-later 

22# -------------------------------------------------------------------------------- 

23# 

24 

25from __future__ import annotations 

26 

27import glob 

28import os 

29import tempfile 

30import unittest 

31 

32import allure 

33import google.protobuf.json_format 

34 

35from klayout_pex.tech_info import TechDefError, TechInfo 

36import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2 

37import klayout_pex_protobuf.kpex.tech.process_stack_pb2 as stack_pb2 

38 

39 

40def tech_pbjson_paths() -> list[str]: 

41 protobuf_dir = os.path.realpath(os.path.join(__file__, '..', '..', 

42 'klayout_pex_protobuf')) 

43 return sorted(glob.glob(os.path.join(protobuf_dir, '*_tech.pb.json'))) 

44 

45 

46def tech_with_duplicates() -> tech_pb2.Technology: 

47 tech = tech_pb2.Technology(name='test') 

48 

49 stack = tech.process_stack 

50 for name in ('met1', 'met2', 'met1'): 

51 layer = stack.layers.add(name=name, 

52 layer_type=stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL) 

53 layer.metal_layer.z = 1.0 

54 layer.metal_layer.thickness = 0.1 

55 # A contact shares the namespace with the layers, so this collides too. 

56 stack.layers[1].metal_layer.contact_above.name = 'met2' 

57 

58 tech.layers.add(name='met1') 

59 tech.layers.add(name='met1') 

60 

61 for name in ('met1_con', 'met1_con'): 

62 tech.lvs_computed_layers.add().layer_info.name = name 

63 

64 return tech 

65 

66 

67@allure.parent_suite('Unit Tests') 

68@allure.tag('TechInfo', 'Technology') 

69class Test(unittest.TestCase): 

70 def test_shipped_tech_definitions_have_unique_names(self): 

71 paths = tech_pbjson_paths() 

72 self.assertNotEqual([], paths, "No generated tech definition to check, " 

73 "run the build first") 

74 for path in paths: 

75 with self.subTest(tech=os.path.basename(path)): 

76 self.assertEqual([], TechInfo.duplicate_names( 

77 TechInfo.parse_tech_def(path))) 

78 

79 def test_duplicate_names_are_reported_per_namespace(self): 

80 problems = TechInfo.duplicate_names(tech_with_duplicates()) 

81 self.assertEqual(4, len(problems), problems) 

82 self.assertIn("the process stack namespace declares 'met1' 2 times", problems[0]) 

83 self.assertIn("the process stack namespace declares 'met2' 2 times", problems[1]) 

84 self.assertIn('as contact, layer', problems[1]) 

85 self.assertIn("the layer namespace declares 'met1' 2 times", problems[2]) 

86 self.assertIn("the LVS computed layer namespace declares 'met1_con' 2 times", 

87 problems[3]) 

88 

89 def test_the_reader_refuses_a_duplicate(self): 

90 with tempfile.TemporaryDirectory() as tmp_dir: 

91 path = os.path.join(tmp_dir, 'duplicates_tech.pb.json') 

92 with open(path, 'w') as f: 

93 f.write(google.protobuf.json_format.MessageToJson(tech_with_duplicates())) 

94 with self.assertRaises(TechDefError): 

95 TechInfo.parse_tech_def(path)