Skip to content

Commit ed93212

Browse files
[nrf noup][ZAP] Extend unit tests to check zap-generate full
Make sure that west zap-generate full that was run with -y argument creates the same content as zap-generate --full. Signed-off-by: Arkadiusz Balys <arkadiusz.balys@nordicsemi.no>
1 parent 9c6dbea commit ed93212

6 files changed

Lines changed: 234 additions & 87 deletions

File tree

.github/workflows/west-zap.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ jobs:
3434
steps:
3535
- name: Checkout
3636
uses: actions/checkout@v5
37-
- name: Checkout submodules & Bootstrap
38-
uses: ./.github/actions/checkout-submodules-and-bootstrap
37+
- name: Checkout submodules
38+
uses: ./.github/actions/checkout-submodules
3939
with:
4040
platform: nrfconnect
4141
- name: Prepare environment

scripts/west/tests/zap_samples.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This file is used to generate the ZAP files for the samples.
2+
# Use this file as an argument to the west zap-generate command:
3+
#
4+
# west zap-generate -y zap_samples.yml
5+
#
6+
7+
# Base dir related to ZEPHYR_BASE
8+
- base_dir: ../modules/lib/matter/test_dir
9+
10+
- name: test
11+
zap_file: test_full.zap
12+
full: true
13+
zcl_file: zcl_appended.json
14+
clusters:
15+
[../scripts/west/tests/Cluster1.xml, ../scripts/west/tests/Cluster2.xml]

scripts/west/tests/zap_tests.py

Lines changed: 178 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
TEST_OBSOLETE_ZAP_FILE = SCRIPT_DIR / "test_obsolete.zap"
3737
APP_TEMPLATES_FILE = MATTER_BASE / DEFAULT_APP_TEMPLATES_RELATIVE_PATH
3838
VERSION_FILE = MATTER_BASE / DEFAULT_ZAP_VERSION_RELATIVE_PATH
39+
TEST_SAMPLES_FILE = SCRIPT_DIR / "zap_samples.yml"
3940

4041

4142
class TestWestZap(unittest.TestCase):
@@ -55,8 +56,10 @@ def setUpClass(cls):
5556
cls.zap_output_dir = cls.test_dir / "zap-generated"
5657
cls.zap_output_dir_full = cls.test_dir / "zap-generated-full"
5758
cls.zap_output_dir_synced = cls.test_dir / "zap-generated-synced"
59+
cls.zap_output_dir_samples_yml = cls.test_dir / "zap-generated-samples-yml"
5860
cls.test_obsolete_zap_file = cls.test_dir / "test_obsolete.zap"
5961
cls.test_obsolete_zcl_file = cls.test_dir / "zcl_test_obsolete.json"
62+
cls.test_samples_file = cls.test_dir / "zap_samples.yml"
6063

6164
cls.cluster_names = [cluster.stem for cluster in cls.test_clusters]
6265

@@ -74,10 +77,14 @@ def setUpClass(cls):
7477
shutil.copy(TEST_ZCL_FILE, cls.zcl_json_file_with_new_items)
7578
shutil.copy(TEST_ZAP_FILE, cls.test_zap_file)
7679
shutil.copy(VERSION_FILE, cls.version_file)
80+
shutil.copy(TEST_SAMPLES_FILE, cls.test_samples_file)
7781

7882
with open(cls.version_file, 'r') as f:
7983
cls.recommended_version = f.read().strip()
8084

85+
# Initialize common zap installer
86+
cls.zap_installer = ZapInstaller(cls.test_dir)
87+
8188
@classmethod
8289
def tearDownClass(cls):
8390
if cls.test_dir.exists():
@@ -155,18 +162,30 @@ def test_post_process_generated_files(self):
155162
with open(test_file, 'w') as f:
156163
f.write("test content")
157164

158-
post_process_generated_files(self.test_dir)
165+
post_process_generated_files(self.test_dir, "manufacturer_specific")
159166

160167
with open(test_file, 'r') as f:
161168
content = f.read()
162169
self.assertTrue(content.endswith('\n'))
163170
self.assertEqual(content, "test content\n")
164171

172+
with open(test_file, 'w') as f:
173+
f.write("# Cluster generated code for constants and metadata based on /home/xxx/ncs/nrf/samples/matter/manufacturer_specific/src/default_zap/manufacturer_specific.matter\n")
174+
f.write("// based on /home/xxx/ncs/nrf/samples/matter/manufacturer_specific/src/default_zap/manufacturer_specific.matter\n")
175+
176+
post_process_generated_files(self.test_dir, "manufacturer_specific")
177+
with open(test_file, 'r') as f:
178+
content = f.readlines()
179+
self.assertEqual(
180+
content[0], "# Cluster generated code for constants and metadata based on manufacturer_specific/src/default_zap/manufacturer_specific.matter\n")
181+
self.assertEqual(
182+
content[1], "// based on manufacturer_specific/src/default_zap/manufacturer_specific.matter\n")
183+
165184
# Test file with multiple newlines
166185
with open(test_file, 'w') as f:
167186
f.write("test content\n\n\n")
168187

169-
post_process_generated_files(self.test_dir)
188+
post_process_generated_files(self.test_dir, "manufacturer_specific")
170189

171190
with open(test_file, 'r') as f:
172191
content = f.read()
@@ -215,17 +234,17 @@ def test_get_paths(self):
215234
- The zap CLI path is returned correctly.
216235
"""
217236
# Install path
218-
installer = ZapInstaller(self.test_dir)
237+
installer = self.zap_installer
219238
expected = self.test_dir / '.zap-install'
220239
self.assertEqual(installer.get_install_path(), expected)
221240

222241
# Zap path
223-
installer = ZapInstaller(self.test_dir)
242+
installer = self.zap_installer
224243
expected = self.test_dir / '.zap-install' / installer.zap_exe
225244
self.assertEqual(installer.get_zap_path(), expected)
226245

227246
# Zap CLI path
228-
installer = ZapInstaller(self.test_dir)
247+
installer = self.zap_installer
229248
expected = self.test_dir / '.zap-install' / installer.zap_cli_exe
230249
self.assertEqual(installer.get_zap_cli_path(), expected)
231250

@@ -238,11 +257,11 @@ def test_version(self):
238257
- The current version is returned correctly.
239258
"""
240259

241-
installer = ZapInstaller(self.test_dir)
260+
installer = self.zap_installer
242261
version = installer.get_recommended_version()
243262
self.assertEqual(version, self.recommended_version)
244263

245-
installer = ZapInstaller(self.test_dir)
264+
installer = self.zap_installer
246265

247266
with patch('subprocess.check_output', side_effect=Exception()):
248267
version = installer.get_current_version()
@@ -277,7 +296,7 @@ def test_install_zap(self):
277296
- The ZAP package is not installed if the current ver sion is the same as the recommended version.
278297
- The ZAP package is installed.
279298
"""
280-
zap_installer = ZapInstaller(self.test_dir)
299+
zap_installer = self.zap_installer
281300

282301
# Test when the current version is the same as the recommended version
283302
with patch.object(zap_installer, 'get_current_version', return_value=self.recommended_version):
@@ -314,19 +333,20 @@ def test_zap_generate(self):
314333
- Zap files are generated correctly.
315334
- The data model is re-generated for --full argument and all zap files for new clusters are generated.
316335
"""
317-
zap_installer = ZapInstaller(self.test_dir)
336+
zap_installer = self.zap_installer
318337
self.assertTrue(zap_installer.get_current_version() != "")
319338

320339
# Run zap-generate command for simple generation
321340
with patch('zap_generate.get_zap_generate_path', return_value=MATTER_BASE / DEFAULT_ZAP_GENERATE_RELATIVE_PATH):
322341
with patch('zap_generate.get_app_templates_path', return_value=MATTER_BASE / DEFAULT_APP_TEMPLATES_RELATIVE_PATH):
323-
ZapGenerate().do_run(Namespace(zap_file=self.test_zap_file,
324-
output=self.zap_output_dir,
325-
matter_path=self.test_dir,
326-
full=False,
327-
keep_previous=False,
328-
zcl=None,
329-
yaml=None), [])
342+
with patch('zap_generate.ZapInstaller', return_value=zap_installer):
343+
ZapGenerate().do_run(Namespace(zap_file=self.test_zap_file,
344+
output=self.zap_output_dir,
345+
matter_path=self.test_dir,
346+
full=False,
347+
keep_previous=False,
348+
zcl=None,
349+
yaml=None), [])
330350

331351
self.assertTrue(self.zap_output_dir.exists())
332352
self.assertTrue((self.zap_output_dir.parent / "test.matter").exists())
@@ -375,61 +395,157 @@ def test_zap_generate_full(self):
375395
# Use the full zap file to generate the full data model.
376396
with patch('zap_generate.get_zap_generate_path', return_value=MATTER_BASE / DEFAULT_ZAP_GENERATE_RELATIVE_PATH):
377397
with patch('zap_generate.get_app_templates_path', return_value=MATTER_BASE / DEFAULT_APP_TEMPLATES_RELATIVE_PATH):
378-
ZapGenerate().do_run(Namespace(zap_file=self.test_zap_file_full,
379-
output=self.zap_output_dir_full,
380-
matter_path=MATTER_BASE,
381-
full=True,
382-
keep_previous=False,
383-
zcl=self.zcl_json_appended,
384-
yaml=None), [])
398+
with patch('zap_generate.ZapInstaller', return_value=self.zap_installer):
399+
ZapGenerate().do_run(Namespace(zap_file=self.test_zap_file_full,
400+
output=self.zap_output_dir_full,
401+
matter_path=MATTER_BASE,
402+
full=True,
403+
keep_previous=False,
404+
zcl=self.zcl_json_appended,
405+
yaml=None), [])
385406

386407
# Check full generation
387408
self._check_full_generation(self.zap_output_dir_full, self.cluster_names)
388409

389-
def test_zap_synchronize(self):
410+
def test_generate_from_yaml(self):
390411
"""
391-
Checks whether the zap_sync function synchronizes the ZAP file correctly.
392-
393-
We expect:
394-
- The ZAP file still contains the custom clusters after synchronization.
395-
- The zcl.json file is synchronized with the Matter SDK, so contains the new items from the Matter SDK, but also contains the custom clusters.
396-
- West zap-generate still works without any errors.
412+
Checks whether the zap_generate function generates the ZAP package correctly from a yaml file.
397413
"""
398414

399-
# Input files should exist.
400-
self.assertTrue(self.zcl_json_appended.exists())
401-
shutil.copy(TEST_ZAP_FILE_FULL, self.test_zap_file_full)
402-
# self.assertTrue(self.test_zap_file_full.exists())
403-
404-
# Copy the obsolete zcl.json file to the test directory.
405-
shutil.copy(TEST_OBSOLETE_ZCL_FILE, self.test_obsolete_zcl_file)
406-
# Copy the obsolete zap file to the test directory.
407-
shutil.copy(TEST_OBSOLETE_ZAP_FILE, self.test_obsolete_zap_file)
408-
409-
# Run zap-sync command
410-
with patch('zap_common.get_rules_path', return_value=MATTER_BASE / DEFAULT_RULES_RELATIVE_PATH):
411-
ZapSync().do_run(Namespace(zap_file=self.test_obsolete_zap_file, zcl_json=self.test_obsolete_zcl_file,
412-
matter_path=MATTER_BASE, clusters=self.test_clusters), [])
413-
414-
# Check whether the custom clusters are still present in the ZAP file.
415-
with open(self.test_obsolete_zap_file, 'r') as f:
416-
content = f.read()
417-
self.assertIn("Cluster 1", content)
418-
self.assertIn("Cluster 2", content)
419-
# Check whether the custom clusters are still present in the zcl.json file.
420-
with open(self.test_obsolete_zcl_file, 'r') as f:
421-
content = f.read()
422-
self.assertIn("Cluster1", content)
423-
self.assertIn("Cluster2", content)
424-
425-
# Run zap-generate command
415+
# Copy the zap file and zcl to compare later.
416+
zap_to_compare = self.test_dir / "zap_to_comapre.zap"
417+
zcl_to_compare = self.test_dir / "zcl_to_compare.json"
418+
shutil.copy(self.test_zap_file_full, zap_to_compare)
419+
shutil.copy(self.zcl_json_appended, zcl_to_compare)
420+
421+
# Replace the base_dir relative to the ZEPHYR_BASE directory.
422+
with open(self.test_samples_file, 'r') as f:
423+
ZEPHYR_BASE = os.environ.get('ZEPHYR_BASE', "")
424+
samples_yml_content = f.read()
425+
samples_yml_content = samples_yml_content.replace(
426+
"base_dir: ../modules/lib/matter/test_dir", f"base_dir: {self.test_dir.relative_to(Path(ZEPHYR_BASE), walk_up=True)}")
427+
print(f"\n\nsamples_yml_content: {samples_yml_content}\n\n")
428+
with open(self.test_samples_file, 'w') as f:
429+
f.write(samples_yml_content)
430+
431+
# Run generate using the yaml file
426432
with patch('zap_generate.get_zap_generate_path', return_value=MATTER_BASE / DEFAULT_ZAP_GENERATE_RELATIVE_PATH):
427433
with patch('zap_generate.get_app_templates_path', return_value=MATTER_BASE / DEFAULT_APP_TEMPLATES_RELATIVE_PATH):
428-
ZapGenerate().do_run(Namespace(zap_file=self.test_obsolete_zap_file, output=self.zap_output_dir_synced,
429-
matter_path=MATTER_BASE, full=True, keep_previous=False, zcl=self.test_obsolete_zcl_file, yaml=None), [])
434+
with patch('zap_generate.ZapInstaller', return_value=self.zap_installer):
435+
ZapGenerate().do_run(Namespace(zap_file=None, output=self.zap_output_dir_samples_yml,
436+
matter_path=MATTER_BASE, full=None, keep_previous=False, zcl=None, yaml=self.test_samples_file), [])
430437

431438
# Check full generation
432-
self._check_full_generation(self.zap_output_dir_synced, self.cluster_names)
439+
self._check_full_generation(self.zap_output_dir_samples_yml, self.cluster_names)
440+
441+
# Check whether all generated files are the same as the ones generated from the zap_generate_full test.
442+
failures = []
443+
444+
# Recursively collect all files from both directories
445+
def collect_files(directory):
446+
"""Recursively collect all files in a directory."""
447+
files = {}
448+
for file_path in directory.rglob("*"):
449+
if file_path.is_file():
450+
# Get relative path from the directory root
451+
rel_path = file_path.relative_to(directory)
452+
files[rel_path] = file_path
453+
return files
454+
455+
full_files = collect_files(self.zap_output_dir_full)
456+
samples_yml_files = collect_files(self.zap_output_dir_samples_yml)
457+
458+
# Check all files from zap_output_dir_full
459+
for rel_path, full_file in full_files.items():
460+
samples_yml_file = self.zap_output_dir_samples_yml / rel_path
461+
462+
if rel_path not in samples_yml_files:
463+
failures.append(f"File missing in samples_yml: {rel_path}")
464+
else:
465+
try:
466+
with open(full_file, 'r', encoding='utf-8', errors='ignore') as f:
467+
full_content = f.read()
468+
with open(samples_yml_file, 'r', encoding='utf-8', errors='ignore') as f:
469+
samples_yml_content = f.read()
470+
471+
if full_content != samples_yml_content:
472+
failures.append(f"File content differs: {rel_path}")
473+
except Exception as e:
474+
failures.append(f"Error comparing file {rel_path}: {str(e)}")
475+
476+
# Check for files in samples_yml that are not in full
477+
for rel_path in samples_yml_files:
478+
if rel_path not in full_files:
479+
failures.append(f"Extra file in samples_yml: {rel_path}")
480+
481+
# Print all failures
482+
if failures:
483+
print("\n" + "=" * 80)
484+
print(f"Found {len(failures)} file comparison failure(s):")
485+
print("=" * 80)
486+
for failure in failures:
487+
print(f" - {failure}")
488+
print("=" * 80 + "\n")
489+
490+
# Assert that there are no failures
491+
self.assertEqual(len(failures), 0, f"Found {len(failures)} file comparison failure(s). See output above for details.")
492+
493+
# Compare zap_from_yml.zap with self.test_zap_file_full
494+
with open(zap_to_compare, "rb") as f1, open(self.test_zap_file_full, "rb") as f2:
495+
zap_to_compare_content = f1.read()
496+
zap_full_content = f2.read()
497+
self.assertEqual(zap_to_compare_content, zap_full_content, "zap_to_compare.zap and test_zap_file_full differ")
498+
499+
# Compare zcl_from_yml.json with zcl_json_appended
500+
with open(zcl_to_compare, "rb") as f1, open(self.zcl_json_appended, "rb") as f2:
501+
zcl_to_compare_content = f1.read()
502+
zcl_appended_content = f2.read()
503+
self.assertEqual(zcl_to_compare_content, zcl_appended_content, "zcl_to_compare.json and zcl_json_appended differ")
504+
505+
# def test_zap_synchronize(self):
506+
# """
507+
# Checks whether the zap_sync function synchronizes the ZAP file correctly.
508+
509+
# We expect:
510+
# - The ZAP file still contains the custom clusters after synchronization.
511+
# - The zcl.json file is synchronized with the Matter SDK, so contains the new items from the Matter SDK, but also contains the custom clusters.
512+
# - West zap-generate still works without any errors.
513+
# """
514+
515+
# # Input files should exist.
516+
# self.assertTrue(self.zcl_json_appended.exists())
517+
# shutil.copy(TEST_ZAP_FILE_FULL, self.test_zap_file_full)
518+
519+
# # Copy the obsolete zcl.json file to the test directory.
520+
# shutil.copy(TEST_OBSOLETE_ZCL_FILE, self.test_obsolete_zcl_file)
521+
# # Copy the obsolete zap file to the test directory.
522+
# shutil.copy(TEST_OBSOLETE_ZAP_FILE, self.test_obsolete_zap_file)
523+
524+
# # Run zap-sync command
525+
# with patch('zap_common.get_rules_path', return_value=MATTER_BASE / DEFAULT_RULES_RELATIVE_PATH):
526+
# ZapSync().do_run(Namespace(zap_file=self.test_obsolete_zap_file, zcl_json=self.test_obsolete_zcl_file,
527+
# matter_path=MATTER_BASE, clusters=self.test_clusters), [])
528+
529+
# # Check whether the custom clusters are still present in the ZAP file.
530+
# with open(self.test_obsolete_zap_file, 'r') as f:
531+
# content = f.read()
532+
# self.assertIn("Cluster 1", content)
533+
# self.assertIn("Cluster 2", content)
534+
# # Check whether the custom clusters are still present in the zcl.json file.
535+
# with open(self.test_obsolete_zcl_file, 'r') as f:
536+
# content = f.read()
537+
# self.assertIn("Cluster1", content)
538+
# self.assertIn("Cluster2", content)
539+
540+
# # Run zap-generate command
541+
# with patch('zap_generate.get_zap_generate_path', return_value=MATTER_BASE / DEFAULT_ZAP_GENERATE_RELATIVE_PATH):
542+
# with patch('zap_generate.get_app_templates_path', return_value=MATTER_BASE / DEFAULT_APP_TEMPLATES_RELATIVE_PATH):
543+
# with patch('zap_generate.ZapInstaller', return_value=self.zap_installer):
544+
# ZapGenerate().do_run(Namespace(zap_file=self.test_obsolete_zap_file, output=self.zap_output_dir_synced,
545+
# matter_path=MATTER_BASE, full=True, keep_previous=False, zcl=self.test_obsolete_zcl_file, yaml=None), [])
546+
547+
# # Check full generation
548+
# self._check_full_generation(self.zap_output_dir_synced, self.cluster_names)
433549

434550
def _check_full_generation(self, output_dir, clusters):
435551
self.assertTrue(output_dir.exists())
@@ -521,7 +637,8 @@ def suite():
521637
'test_zap_generate',
522638
'test_zap_append',
523639
'test_zap_generate_full',
524-
'test_zap_synchronize'
640+
'test_generate_from_yaml',
641+
# 'test_zap_synchronize',
525642
]
526643

527644
loader = unittest.TestLoader()

0 commit comments

Comments
 (0)