feat: check rule format with pytest
							parent
							
								
									42399ff1f6
								
							
						
					
					
						commit
						d79f13e76f
					
				| @ -0,0 +1,27 @@ | |||||||
|  | name: Tests | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |   workflow_dispatch: | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   check-rule-format: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [validate-lint] | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout the repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  | 
 | ||||||
|  |       - name: Install dependencies | ||||||
|  |         run: | | ||||||
|  |           python -m pip install --upgrade pip | ||||||
|  |           pip install pytest pytest-asyncio | ||||||
|  | 
 | ||||||
|  |       - name: Check rule format with pytest | ||||||
|  |         run: | | ||||||
|  |           pytest -v -s ./test/check_rule_format.py | ||||||
| @ -1,41 +0,0 @@ | |||||||
| """Check if a file is a valid JSON file. |  | ||||||
| 
 |  | ||||||
| Usage: |  | ||||||
| python json_check.py [JSON file path] |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| python json_check.py multi_lang.json |  | ||||||
| """ |  | ||||||
| import argparse |  | ||||||
| import json |  | ||||||
| import sys |  | ||||||
| import os |  | ||||||
| 
 |  | ||||||
| def check_json_file(file_path): |  | ||||||
|     try: |  | ||||||
|         with open(file_path, "r", encoding="utf-8") as file: |  | ||||||
|             json.load(file) |  | ||||||
|             return True |  | ||||||
|     except FileNotFoundError: |  | ||||||
|         print(file_path, "is not found.") |  | ||||||
|         return False |  | ||||||
|     except json.JSONDecodeError: |  | ||||||
|         print(file_path, "is not a valid JSON file.") |  | ||||||
|         return False |  | ||||||
| 
 |  | ||||||
| def main(): |  | ||||||
|     parser = argparse.ArgumentParser( |  | ||||||
|         description="Check if a file is a valid JSON file.") |  | ||||||
|     parser.add_argument("file_path", help="JSON file path") |  | ||||||
|     args = parser.parse_args() |  | ||||||
|     script_name = os.path.basename(__file__) |  | ||||||
|     file_name = os.path.basename(args.file_path) |  | ||||||
| 
 |  | ||||||
|     if not check_json_file(args.file_path): |  | ||||||
|         print(args.file_path, script_name, "FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
| 
 |  | ||||||
|     print(script_name, file_name, "PASS") |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     main() |  | ||||||
| @ -1,127 +0,0 @@ | |||||||
| """Check if conversion rules are valid. |  | ||||||
| 
 |  | ||||||
| The files to be checked are in the directory of ../miot/specs/ |  | ||||||
| To run this script, PYTHONPATH must be set first. |  | ||||||
| See test_all.sh for the usage. |  | ||||||
| 
 |  | ||||||
| You can run all tests by running: |  | ||||||
| ``` |  | ||||||
| ./test_all.sh |  | ||||||
| ``` |  | ||||||
| """ |  | ||||||
| import sys |  | ||||||
| import os |  | ||||||
| import json |  | ||||||
| 
 |  | ||||||
| def load_json(file_path: str) -> dict: |  | ||||||
|     """Load json file.""" |  | ||||||
|     with open(file_path, "r", encoding="utf-8") as file: |  | ||||||
|         data = json.load(file) |  | ||||||
|         return data |  | ||||||
| 
 |  | ||||||
| def dict_str_str(d: dict) -> bool: |  | ||||||
|     """restricted format: dict[str, str]""" |  | ||||||
|     if not isinstance(d, dict): |  | ||||||
|         return False |  | ||||||
|     for k, v in d.items(): |  | ||||||
|         if not isinstance(k, str) or not isinstance(v, str): |  | ||||||
|             return False |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| def dict_str_dict(d: dict) -> bool: |  | ||||||
|     """restricted format: dict[str, dict]""" |  | ||||||
|     if not isinstance(d, dict): |  | ||||||
|         return False |  | ||||||
|     for k, v in d.items(): |  | ||||||
|         if not isinstance(k, str) or not isinstance(v, dict): |  | ||||||
|             return False |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| def nested_2_dict_str_str(d: dict) -> bool: |  | ||||||
|     """restricted format: dict[str, dict[str, str]]""" |  | ||||||
|     if not dict_str_dict(d): |  | ||||||
|         return False |  | ||||||
|     for v in d.values(): |  | ||||||
|         if not dict_str_str(v): |  | ||||||
|             return False |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| def nested_3_dict_str_str(d: dict) -> bool: |  | ||||||
|     """restricted format: dict[str, dict[str, dict[str, str]]]""" |  | ||||||
|     if not dict_str_dict(d): |  | ||||||
|         return False |  | ||||||
|     for v in d.values(): |  | ||||||
|         if not nested_2_dict_str_str(v): |  | ||||||
|             return False |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| def spec_filter(d: dict) -> bool: |  | ||||||
|     """restricted format: dict[str, dict[str, list<str>]]""" |  | ||||||
|     if not dict_str_dict(d): |  | ||||||
|         return False |  | ||||||
|     for value in d.values(): |  | ||||||
|         for k, v in value.items(): |  | ||||||
|             if not isinstance(k, str) or not isinstance(v, list): |  | ||||||
|                 return False |  | ||||||
|             if not all(isinstance(i, str) for i in v): |  | ||||||
|                 return False |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| def bool_trans(d: dict) -> bool: |  | ||||||
|     """dict[str,  dict[str, str] | dict[str, dict[str, str]] ]""" |  | ||||||
|     if not isinstance(d, dict): |  | ||||||
|         return False |  | ||||||
|     if "data" not in d or "translate" not in d: |  | ||||||
|         return False |  | ||||||
|     if not dict_str_str(d["data"]): |  | ||||||
|         return False |  | ||||||
|     if not nested_3_dict_str_str(d["translate"]): |  | ||||||
|         return False |  | ||||||
| 
 |  | ||||||
|     return True |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def main(): |  | ||||||
|     script_name = os.path.basename(__file__) |  | ||||||
| 
 |  | ||||||
|     source_dir = "../miot/specs" |  | ||||||
|     if not bool_trans(load_json(f"{source_dir}/bool_trans.json")): |  | ||||||
|         print(script_name, "bool_trans FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/multi_lang.json")): |  | ||||||
|         print(script_name, "multi_lang FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not spec_filter(load_json(f"{source_dir}/spec_filter.json")): |  | ||||||
|         print(script_name, "spec_filter FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
| 
 |  | ||||||
|     source_dir = "../miot/i18n" |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/de.json")): |  | ||||||
|         print(script_name, "i18n de.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/en.json")): |  | ||||||
|         print(script_name, "i18n en.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/es.json")): |  | ||||||
|         print(script_name, "i18n es.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/fr.json")): |  | ||||||
|         print(script_name, "i18n fr.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/ja.json")): |  | ||||||
|         print(script_name, "i18n ja.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/ru.json")): |  | ||||||
|         print(script_name, "i18n ru.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/zh-Hans.json")): |  | ||||||
|         print(script_name, "i18n zh-Hans.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
|     if not nested_3_dict_str_str(load_json(f"{source_dir}/zh-Hant.json")): |  | ||||||
|         print(script_name, "i18n zh-Hant.json FAIL") |  | ||||||
|         sys.exit(1) |  | ||||||
| 
 |  | ||||||
|     print(script_name, "PASS") |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     main() |  | ||||||
| @ -1,29 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| 
 |  | ||||||
| # Get the script path. |  | ||||||
| script_path=$(dirname "$0") |  | ||||||
| # Change to the script path. |  | ||||||
| cd "$script_path" |  | ||||||
| # Set PYTHONPATH. |  | ||||||
| cd .. |  | ||||||
| export PYTHONPATH=`pwd` |  | ||||||
| echo "PYTHONPATH=$PYTHONPATH" |  | ||||||
| cd - |  | ||||||
| 
 |  | ||||||
| # Run the tests. |  | ||||||
| export source_dir="../miot/specs" |  | ||||||
| python3 json_format.py $source_dir/bool_trans.json |  | ||||||
| python3 json_format.py $source_dir/multi_lang.json |  | ||||||
| python3 json_format.py $source_dir/spec_filter.json |  | ||||||
| export source_dir="../miot/i18n" |  | ||||||
| python3 json_format.py $source_dir/de.json |  | ||||||
| python3 json_format.py $source_dir/en.json |  | ||||||
| python3 json_format.py $source_dir/es.json |  | ||||||
| python3 json_format.py $source_dir/fr.json |  | ||||||
| python3 json_format.py $source_dir/ja.json |  | ||||||
| python3 json_format.py $source_dir/ru.json |  | ||||||
| python3 json_format.py $source_dir/zh-Hans.json |  | ||||||
| python3 json_format.py $source_dir/zh-Hant.json |  | ||||||
| python3 rule_format.py |  | ||||||
| @ -0,0 +1,122 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | """Test rule format.""" | ||||||
|  | import json | ||||||
|  | from os import listdir, path | ||||||
|  | from typing import Optional | ||||||
|  | 
 | ||||||
|  | SOURCE_DIR: str = path.dirname(path.abspath(__file__)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def load_json_file(file_path: str) -> Optional[dict]: | ||||||
|  |     try: | ||||||
|  |         with open(file_path, 'r', encoding='utf-8') as file: | ||||||
|  |             return json.load(file) | ||||||
|  |     except FileNotFoundError: | ||||||
|  |         print(file_path, 'is not found.') | ||||||
|  |         return None | ||||||
|  |     except json.JSONDecodeError: | ||||||
|  |         print(file_path, 'is not a valid JSON file.') | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def dict_str_str(d: dict) -> bool: | ||||||
|  |     """restricted format: dict[str, str]""" | ||||||
|  |     if not isinstance(d, dict): | ||||||
|  |         return False | ||||||
|  |     for k, v in d.items(): | ||||||
|  |         if not isinstance(k, str) or not isinstance(v, str): | ||||||
|  |             return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def dict_str_dict(d: dict) -> bool: | ||||||
|  |     """restricted format: dict[str, dict]""" | ||||||
|  |     if not isinstance(d, dict): | ||||||
|  |         return False | ||||||
|  |     for k, v in d.items(): | ||||||
|  |         if not isinstance(k, str) or not isinstance(v, dict): | ||||||
|  |             return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def nested_2_dict_str_str(d: dict) -> bool: | ||||||
|  |     """restricted format: dict[str, dict[str, str]]""" | ||||||
|  |     if not dict_str_dict(d): | ||||||
|  |         return False | ||||||
|  |     for v in d.values(): | ||||||
|  |         if not dict_str_str(v): | ||||||
|  |             return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def nested_3_dict_str_str(d: dict) -> bool: | ||||||
|  |     """restricted format: dict[str, dict[str, dict[str, str]]]""" | ||||||
|  |     if not dict_str_dict(d): | ||||||
|  |         return False | ||||||
|  |     for v in d.values(): | ||||||
|  |         if not nested_2_dict_str_str(v): | ||||||
|  |             return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def spec_filter(d: dict) -> bool: | ||||||
|  |     """restricted format: dict[str, dict[str, list<str>]]""" | ||||||
|  |     if not dict_str_dict(d): | ||||||
|  |         return False | ||||||
|  |     for value in d.values(): | ||||||
|  |         for k, v in value.items(): | ||||||
|  |             if not isinstance(k, str) or not isinstance(v, list): | ||||||
|  |                 return False | ||||||
|  |             if not all(isinstance(i, str) for i in v): | ||||||
|  |                 return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def bool_trans(d: dict) -> bool: | ||||||
|  |     """dict[str,  dict[str, str] | dict[str, dict[str, str]] ]""" | ||||||
|  |     if not isinstance(d, dict): | ||||||
|  |         return False | ||||||
|  |     if 'data' not in d or 'translate' not in d: | ||||||
|  |         return False | ||||||
|  |     if not dict_str_str(d['data']): | ||||||
|  |         return False | ||||||
|  |     if not nested_3_dict_str_str(d['translate']): | ||||||
|  |         return False | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_bool_trans(): | ||||||
|  |     data: dict = load_json_file( | ||||||
|  |         path.join( | ||||||
|  |             SOURCE_DIR, | ||||||
|  |             '../custom_components/xiaomi_home/miot/specs/bool_trans.json')) | ||||||
|  |     assert data | ||||||
|  |     assert bool_trans(data) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_spec_filter(): | ||||||
|  |     data: dict = load_json_file( | ||||||
|  |         path.join( | ||||||
|  |             SOURCE_DIR, | ||||||
|  |             '../custom_components/xiaomi_home/miot/specs/spec_filter.json')) | ||||||
|  |     assert data | ||||||
|  |     assert spec_filter(data) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_multi_lang(): | ||||||
|  |     data: dict = load_json_file( | ||||||
|  |         path.join( | ||||||
|  |             SOURCE_DIR, | ||||||
|  |             '../custom_components/xiaomi_home/miot/specs/multi_lang.json')) | ||||||
|  |     assert data | ||||||
|  |     assert nested_3_dict_str_str(data) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_miot_i18n(): | ||||||
|  |     i18n_path: str = path.join( | ||||||
|  |         SOURCE_DIR, '../custom_components/xiaomi_home/miot/i18n') | ||||||
|  |     for file_name in listdir(i18n_path): | ||||||
|  |         file_path: str = path.join(i18n_path, file_name) | ||||||
|  |         data: dict = load_json_file(file_path) | ||||||
|  |         assert data | ||||||
|  |         assert nested_3_dict_str_str(data) | ||||||
					Loading…
					
					
				
		Reference in New Issue