feat: check rule format with pytest

This commit is contained in:
topsworld 2024-12-12 20:21:54 +08:00 committed by Guoliang Li
parent 42399ff1f6
commit d79f13e76f
6 changed files with 153 additions and 210 deletions

27
.github/workflows/test.yaml vendored Normal file
View File

@ -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

View File

@ -3,10 +3,11 @@ name: Validate
on:
push:
branches:
- main
- main
pull_request:
branches:
- main
- main
workflow_dispatch:
jobs:
validate-hassfest:
@ -30,16 +31,6 @@ jobs:
category: integration
ignore: brands
validate-format:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Check format
run: |
./custom_components/xiaomi_home/test/test_all.sh
validate-lint:
runs-on: ubuntu-latest
steps:
@ -51,7 +42,7 @@ jobs:
python -m pip install --upgrade pip
pip install pylint
- name: Analyse the code with pylint
- name: Static analyse the code with pylint
run: |
pylint $(git ls-files '*.py')

View File

@ -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()

View File

@ -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()

View File

@ -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

122
test/check_rule_format.py Normal file
View File

@ -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)