Coverage for tests/pex25d/artifact_test.py: 100%

57 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 allure 

28import os 

29import tempfile 

30import unittest 

31 

32from klayout_pex.pex25d.artifact import ( 

33 ArtifactFormat, ArtifactKind, ArtifactNamingError, STDIO_PATH, 

34 infer_artifact_spec, 

35) 

36from klayout_pex.pex25d.codec import load_artifact, save_artifact 

37from klayout_pex.pex25d.resolver import resolve 

38 

39from .pex25d_fixtures import MINIMAL, read 

40 

41 

42@allure.parent_suite("Unit Tests") 

43@allure.tag("PEX25D", "Artifact") 

44class Pex25DArtifactTest(unittest.TestCase): 

45 # ------------------------------------------------------------ inference 

46 

47 def test_the_naming_convention(self): 

48 cases = { 

49 'x.pex25d': (ArtifactKind.FILE, ArtifactFormat.TEXT, False), 

50 'x.pex25d.pb': (ArtifactKind.FILE, ArtifactFormat.PB, False), 

51 'x.pex25d.textpb': (ArtifactKind.FILE, ArtifactFormat.TEXTPB, False), 

52 'x.pex25d.scene.pb': (ArtifactKind.SCENE, ArtifactFormat.PB, False), 

53 'x.pex25d.scene.textpb': (ArtifactKind.SCENE, ArtifactFormat.TEXTPB, False), 

54 'x.pex25d.gz': (ArtifactKind.FILE, ArtifactFormat.TEXT, True), 

55 'x.pex25d.scene.pb.gz': (ArtifactKind.SCENE, ArtifactFormat.PB, True), 

56 } 

57 for path, (kind, format, gzipped) in cases.items(): 

58 with self.subTest(path=path): 

59 spec = infer_artifact_spec(path) 

60 assert (spec.kind, spec.format, spec.gzipped) == (kind, format, gzipped) 

61 

62 def test_an_unconventional_name_has_to_be_stated(self): 

63 with self.assertRaises(ArtifactNamingError): 

64 infer_artifact_spec('/tmp/whatever.dat') 

65 spec = infer_artifact_spec('/tmp/whatever.dat', 

66 kind=ArtifactKind.SCENE, 

67 format=ArtifactFormat.PB) 

68 assert (spec.kind, spec.format) == (ArtifactKind.SCENE, ArtifactFormat.PB) 

69 

70 def test_an_explicit_choice_beats_the_name(self): 

71 spec = infer_artifact_spec('x.pex25d', format=ArtifactFormat.TEXTPB) 

72 assert spec.format == ArtifactFormat.TEXTPB 

73 assert spec.kind == ArtifactKind.FILE 

74 

75 def test_stdio_falls_back_to_the_defaults(self): 

76 spec = infer_artifact_spec(STDIO_PATH) 

77 assert spec.is_stdio 

78 assert (spec.kind, spec.format) == (ArtifactKind.FILE, ArtifactFormat.TEXT) 

79 

80 def test_binary_formats_are_flagged(self): 

81 assert infer_artifact_spec('x.pex25d.pb').is_binary 

82 assert infer_artifact_spec('x.pex25d.gz').is_binary 

83 assert not infer_artifact_spec('x.pex25d').is_binary 

84 assert not infer_artifact_spec('x.pex25d.textpb').is_binary 

85 

86 # ----------------------------------------------------------- round trip 

87 

88 def round_trip(self, message, name: str): 

89 with tempfile.TemporaryDirectory() as directory: 

90 spec = infer_artifact_spec(os.path.join(directory, name)) 

91 save_artifact(message, spec) 

92 reloaded = load_artifact(spec) 

93 assert reloaded.SerializeToString(deterministic=True) == \ 

94 message.SerializeToString(deterministic=True) 

95 

96 def test_a_file_survives_every_encoding(self): 

97 message = read(MINIMAL) 

98 for name in ('a.pex25d', 'a.pex25d.pb', 'a.pex25d.textpb', 

99 'a.pex25d.gz', 'a.pex25d.pb.gz'): 

100 with self.subTest(name=name): 

101 self.round_trip(message, name) 

102 

103 def test_a_scene_survives_the_protobuf_encodings(self): 

104 scene = resolve(read(MINIMAL)) 

105 for name in ('a.pex25d.scene.pb', 'a.pex25d.scene.textpb', 

106 'a.pex25d.scene.pb.gz'): 

107 with self.subTest(name=name): 

108 self.round_trip(scene, name) 

109 

110 def test_a_scene_has_no_text_spelling(self): 

111 # Resolution is not reversible; asking for a text scene is refused when 

112 # the spec is built, rather than silently lowered when it is written. 

113 with self.assertRaises(ArtifactNamingError): 

114 infer_artifact_spec('a.pex25d', kind=ArtifactKind.SCENE) 

115 with self.assertRaises(ArtifactNamingError): 

116 infer_artifact_spec('a.pex25d.scene.pb', format=ArtifactFormat.TEXT)