Skip to content

Commit 23557dd

Browse files
authored
test sdk: Increase test coverage (#483)
Previously the test coverage in the `basyx.aas.adapter.aasx` package was suboptimal. The `AASXWriter` class was tested end-to-end in combination with `AASXReader` but special cases, that lead to exceptions were not covered. Additionally the `load_directory(...)` method of the `basyx.aas.adapter` package was untested. With these changes additional tests are added which cover the code parts that are mentioned above.
1 parent 9dbb62f commit 23557dd

4 files changed

Lines changed: 392 additions & 4 deletions

File tree

sdk/test/adapter/aasx/TestFile.pdf

594 KB
Binary file not shown.

sdk/test/adapter/aasx/test.png

834 Bytes
Loading

sdk/test/adapter/aasx/test_aasx.py

Lines changed: 292 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import tempfile
1212
import unittest
1313
import warnings
14+
from pathlib import Path
1415

1516
import pyecma376_2
1617
from basyx.aas import model
@@ -62,14 +63,14 @@ def test_supplementary_file_container(self) -> None:
6263

6364
# Check metadata
6465
self.assertEqual("application/pdf", container.get_content_type("/TestFile.pdf"))
65-
self.assertEqual("b18229b24a4ee92c6c2b6bc6a8018563b17472f1150d35d5a5945afeb447ed44",
66+
self.assertEqual("142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342",
6667
container.get_sha256("/TestFile.pdf").hex())
6768
self.assertIn("/TestFile.pdf", container)
6869

6970
# Check contents
7071
file_content = io.BytesIO()
7172
container.write_file("/TestFile.pdf", file_content)
72-
self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "78450a66f59d74c073bf6858db340090ea72a8b1")
73+
self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1")
7374

7475
# Add same file again with different content_type to test reference counting
7576
with open(__file__, 'rb') as f:
@@ -90,6 +91,249 @@ def test_supplementary_file_container(self) -> None:
9091

9192

9293
class AASXWriterTest(unittest.TestCase):
94+
def test_write_missing_aas_objects(self):
95+
with tempfile.TemporaryDirectory() as tmpdir:
96+
tmpdir_path = Path(tmpdir)
97+
98+
# ---- Arange ----
99+
data = example_aas.create_full_example()
100+
101+
# ---- Act & Assert -----
102+
with self.assertLogs(level="WARNING") as log:
103+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
104+
# try to write non-existing object
105+
writer.write_aas_objects(
106+
"/aasx/selection.xml",
107+
["https://example.org/Test_AssetAdministrationShell",
108+
"http://false-identifier.org/",
109+
"http://example.org/Submodels/Assets/TestAsset/Identification"],
110+
data, aasx.DictSupplementaryFileContainer()
111+
)
112+
113+
self.assertIn("Could not find identifiable http://false-identifier.org/ in IdentifiableStore",
114+
log.output[0])
115+
116+
# assert only the two existing objects have been written to aasx file
117+
object_store = model.DictIdentifiableStore()
118+
with aasx.AASXReader(tmpdir_path / "tmp.aasx") as reader:
119+
reader.read_into(object_store, aasx.DictSupplementaryFileContainer())
120+
self.assertEqual(len(object_store), 2)
121+
122+
def test_writing_with_missing_file(self) -> None:
123+
with tempfile.TemporaryDirectory() as tmpdir:
124+
tmpdir_path = Path(tmpdir)
125+
126+
# ---- Arange ----
127+
# data contains a submodel with a File submodel_element
128+
# the empty_file_store does not contain the referenced file
129+
data = example_aas.create_full_example()
130+
empty_file_store = aasx.DictSupplementaryFileContainer()
131+
132+
# ---- Act & Assert ----
133+
# assert warning is present in failsafe mode
134+
with self.assertLogs(level="WARNING") as log:
135+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
136+
writer.write_all_aas_objects("/aasx/data.xml", data, empty_file_store)
137+
self.assertIn("Could not find file", log.output[0])
138+
139+
# assert exception is rose in non-failsafe mode
140+
with self.assertRaises(KeyError) as cm:
141+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer:
142+
writer.write_all_aas_objects("/aasx/data.xml", data, empty_file_store)
143+
self.assertIn("Could not find file", cm.exception.args[0])
144+
145+
def test_writing_file_twice(self) -> None:
146+
with (tempfile.TemporaryDirectory() as tmpdir):
147+
tmpdir_path = Path(tmpdir)
148+
149+
# ---- Arange ----
150+
file_store = aasx.DictSupplementaryFileContainer()
151+
with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf:
152+
resulting_file_name = file_store.add_file("/TestFile.pdf", pdf, "application/pdf")
153+
154+
# create two submodels that reference the same file in file_store
155+
first_submodel = model.Submodel(
156+
id_="http://example.org/First_Submodel",
157+
submodel_element=[model.File(
158+
id_short="ExampleFile",
159+
content_type="application/pdf",
160+
value=resulting_file_name
161+
)]
162+
)
163+
second_submodel = model.Submodel(
164+
id_="http://example.org/SecondSubmodel",
165+
submodel_element=[model.File(
166+
id_short="ExampleFile",
167+
content_type="application/pdf",
168+
value=resulting_file_name
169+
)]
170+
)
171+
data: model.DictIdentifiableStore[model.Identifiable] \
172+
= model.DictIdentifiableStore([first_submodel, second_submodel])
173+
174+
# ---- Act & Assert ----
175+
with self.assertNoLogs(level="WARNING"):
176+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx") as writer:
177+
writer.write_all_aas_objects("/aasx/data.xml", data, file_store)
178+
179+
def test_write_non_aas(self) -> None:
180+
with tempfile.TemporaryDirectory() as tmpdir:
181+
tmpdir_path = Path(tmpdir)
182+
183+
# ---- Arange ----
184+
data = example_aas.create_full_example()
185+
file_store = aasx.DictSupplementaryFileContainer()
186+
with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf:
187+
file_store.add_file("/TestFile.pdf", pdf, "application/pdf")
188+
189+
# ---- Act & Assert ----
190+
# assert warning is present in failsafe mode
191+
with self.assertLogs(level="WARNING") as log:
192+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
193+
# try to write a non AAS object
194+
writer.write_aas("https://example.org/Test_Submodel", data, file_store)
195+
self.assertIn("Skipping AAS https://example.org/Test_Submodel", log.output[0])
196+
197+
# assert exception is rose in non-failsafe mode
198+
with self.assertRaises(TypeError) as cm:
199+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer:
200+
# try to write a non AAS object
201+
writer.write_aas("https://example.org/Test_Submodel", data, file_store)
202+
self.assertIn("Identifier https://example.org/Test_Submodel does not belong "
203+
"to an AssetAdministrationShell", cm.exception.args[0])
204+
205+
def test_write_aas_missing_submodel(self) -> None:
206+
with tempfile.TemporaryDirectory() as tmpdir:
207+
tmpdir_path = Path(tmpdir)
208+
209+
# ---- Arange ----
210+
# leave example_submodel out of object store
211+
data = model.DictIdentifiableStore([
212+
example_aas.create_example_asset_administration_shell(),
213+
example_aas.create_example_asset_identification_submodel(),
214+
example_aas.create_example_bill_of_material_submodel()
215+
])
216+
empty_file_store = aasx.DictSupplementaryFileContainer()
217+
218+
# ---- Act & Assert ----
219+
# assert warning is present in failsafe mode
220+
with self.assertLogs(level="WARNING") as log:
221+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
222+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store)
223+
self.assertIn("Could not find Submodel", log.output[0])
224+
225+
# assert exception is rose in non-failsafe mode
226+
with self.assertRaises(KeyError) as cm:
227+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer:
228+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store)
229+
self.assertIn("Could not find Submodel", cm.exception.args[0])
230+
231+
def test_write_aas_missing_concept_description(self) -> None:
232+
with tempfile.TemporaryDirectory() as tmpdir:
233+
tmpdir_path = Path(tmpdir)
234+
235+
# ---- Arange ----
236+
# leave example_concept_description out of object store
237+
data = model.DictIdentifiableStore([
238+
example_aas.create_example_asset_administration_shell(),
239+
example_aas.create_example_submodel(),
240+
example_aas.create_example_asset_identification_submodel(),
241+
example_aas.create_example_bill_of_material_submodel()
242+
])
243+
file_store = aasx.DictSupplementaryFileContainer()
244+
with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf:
245+
file_store.add_file("/TestFile.pdf", pdf, "application/pdf")
246+
247+
# ---- Act & Assert ----
248+
# assert warning is present in failsafe mode
249+
with self.assertLogs(level="WARNING") as log:
250+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
251+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, file_store)
252+
self.assertIn("https://example.org/Test_ConceptDescription", log.output[0])
253+
self.assertRegex(log.output[0], "ConceptDescription .* not found")
254+
255+
# assert exception is rose in non-failsafe mode
256+
with self.assertRaises(KeyError) as cm:
257+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer:
258+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, file_store)
259+
self.assertIn("https://example.org/Test_ConceptDescription", cm.exception.args[0])
260+
self.assertRegex(cm.exception.args[0], "ConceptDescription .* not found")
261+
262+
def test_write_aas_false_semantic_id(self) -> None:
263+
with tempfile.TemporaryDirectory() as tmpdir:
264+
tmpdir_path = Path(tmpdir)
265+
266+
# ---- Arange ----
267+
# semanticId of submodel holds reference to an object
268+
# that is no ContentDescription
269+
second_submodel = model.Submodel(
270+
id_="https://example.org/Second_Submodel"
271+
)
272+
submodel = model.Submodel(
273+
id_="https://example.org/Test_Submodel",
274+
semantic_id=model.ModelReference(
275+
key=(model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Second_Submodel"),),
276+
type_=model.ConceptDescription
277+
)
278+
)
279+
data = model.DictIdentifiableStore([
280+
example_aas.create_example_asset_administration_shell(),
281+
example_aas.create_example_asset_identification_submodel(),
282+
example_aas.create_example_bill_of_material_submodel(),
283+
submodel, second_submodel
284+
])
285+
empty_file_store = aasx.DictSupplementaryFileContainer()
286+
287+
# ---- Act & Assert ----
288+
# assert warning is present in failsafe mode
289+
with self.assertLogs(level="WARNING") as log:
290+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer:
291+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store)
292+
self.assertIn("which is not a ConceptDescription", log.output[0])
293+
294+
# assert exception is rose in non-failsafe mode
295+
with self.assertRaises(TypeError) as cm:
296+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer:
297+
writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store)
298+
self.assertIn("which is not a ConceptDescription", cm.exception.args[0])
299+
300+
def test_write_core_properties_twice(self) -> None:
301+
with tempfile.TemporaryDirectory() as tmpdir:
302+
tmpdir_path = Path(tmpdir)
303+
304+
# ---- Arrange ----
305+
cp = pyecma376_2.OPCCoreProperties()
306+
cp.created = datetime.datetime.now()
307+
cp.creator = "Eclipse BaSyx Python Testing Framework"
308+
309+
# ---- Act & Assert ----
310+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx") as writer:
311+
writer.write_core_properties(cp)
312+
313+
# expect RuntimeError on second write
314+
with self.assertRaises(RuntimeError) as cm:
315+
writer.write_core_properties(cp)
316+
317+
self.assertIn("Core Properties have already been written", cm.exception.args[0])
318+
319+
def test_write_thumbnail_twice(self) -> None:
320+
with tempfile.TemporaryDirectory() as tmpdir:
321+
tmpdir_path = Path(tmpdir)
322+
323+
# ---- Arrange ----
324+
with open(Path(__file__).parent / "test.png", "rb") as png:
325+
thumbnail = png.read()
326+
327+
# ---- Act & Assert ----
328+
with aasx.AASXWriter(tmpdir_path / "tmp.aasx") as writer:
329+
writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png")
330+
331+
# expect RuntimeError on second write
332+
with self.assertRaises(RuntimeError) as cm:
333+
writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png")
334+
335+
self.assertIn("package thumbnail has already been written", cm.exception.args[0])
336+
93337
def test_writing_reading_example_aas(self) -> None:
94338
# Create example data and file_store
95339
data = example_aas.create_full_example() # creates a complete, valid example AAS
@@ -147,7 +391,7 @@ def test_writing_reading_example_aas(self) -> None:
147391
file_content = io.BytesIO()
148392
new_files.write_file("/TestFile.pdf", file_content)
149393
self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(),
150-
"78450a66f59d74c073bf6858db340090ea72a8b1")
394+
"241e62aef8b4cdad0975f6c68a4ed8b3923d8db1")
151395

152396
os.unlink(filename)
153397

@@ -207,6 +451,50 @@ def test_reading_core_properties(self) -> None:
207451
finally:
208452
os.unlink(filename)
209453

454+
def test_get_thumbnail(self) -> None:
455+
with tempfile.TemporaryDirectory() as tmpdir:
456+
# ---- Arange ----
457+
tmpdir_path = Path(tmpdir)
458+
459+
data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore([
460+
model.AssetAdministrationShell(
461+
id_="http://example.org/Test_AAS",
462+
asset_information=model.AssetInformation(
463+
global_asset_id="http://example.org/Test_Asset"
464+
)
465+
)
466+
])
467+
468+
with aasx.AASXWriter(tmpdir_path / "test_thumbnail.aasx") as writer:
469+
writer.write_aas(
470+
'http://example.org/Test_AAS',
471+
data, aasx.DictSupplementaryFileContainer(), write_json=False
472+
)
473+
with open(Path(__file__).parent / "test.png", "rb") as png:
474+
thumbnail = png.read()
475+
writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png")
476+
477+
# ---- Act ----
478+
with aasx.AASXReader(tmpdir_path / "test_thumbnail.aasx") as reader:
479+
new_thumbnail = reader.get_thumbnail()
480+
481+
# ---- Assert ----
482+
self.assertEqual(new_thumbnail, thumbnail)
483+
484+
def test_missing_thumbnail(self) -> None:
485+
# ---- Arange ----
486+
filename = self._create_test_aasx()
487+
488+
try:
489+
# ---- Act ----
490+
with aasx.AASXReader(filename) as reader:
491+
thumbnail = reader.get_thumbnail()
492+
493+
# ---- Assert ----
494+
self.assertIsNone(thumbnail)
495+
finally:
496+
os.unlink(filename)
497+
210498
def test_read_into(self) -> None:
211499
filename = self._create_test_aasx()
212500

@@ -246,7 +534,7 @@ def test_supplementary_file_integrity(self) -> None:
246534

247535
self.assertEqual(
248536
hashlib.sha1(buf.getvalue()).hexdigest(),
249-
"78450a66f59d74c073bf6858db340090ea72a8b1"
537+
"241e62aef8b4cdad0975f6c68a4ed8b3923d8db1"
250538
)
251539
finally:
252540
os.unlink(filename)

0 commit comments

Comments
 (0)