From cd60103ff282aca0f1e6228907b0e275b74d611e Mon Sep 17 00:00:00 2001 From: shubham kumar Date: Tue, 2 Jun 2026 15:51:52 +0000 Subject: [PATCH 1/4] test: Add new checkers for pyzes Related-To: NEO-18718 Signed-off-by: shubham kumar --- .../bindings-sysman-python-checks.yml | 84 +++++ .../sysman/python/test/validate_structures.py | 301 ++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 .github/workflows/bindings-sysman-python-checks.yml create mode 100644 bindings/sysman/python/test/validate_structures.py diff --git a/.github/workflows/bindings-sysman-python-checks.yml b/.github/workflows/bindings-sysman-python-checks.yml new file mode 100644 index 00000000..effcc6e3 --- /dev/null +++ b/.github/workflows/bindings-sysman-python-checks.yml @@ -0,0 +1,84 @@ +## +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: MIT +# +## + +name: Python Bindings - Validation Checks + +on: + pull_request: + branches: + - '**' + paths: + - 'bindings/sysman/python/**' + push: + branches: + - main + - master + - python_bindings + paths: + - 'bindings/sysman/python/**' + workflow_dispatch: + +env: + PYTHON_VERSION: '3.10' + +jobs: + validation-checks: + name: Validation Checks + runs-on: ubuntu-latest + defaults: + run: + working-directory: bindings/sysman/python + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Create virtual environment and install + run: | + python -m venv .venv + source .venv/bin/activate + pip install --upgrade pip --quiet + pip install -e . --quiet + + - name: Run validation checks + run: | + source .venv/bin/activate + + # Check 1: Verify installation location + PYZES_LOCATION=$(python -c "import pyzes; print(pyzes.__file__ if hasattr(pyzes, '__file__') else 'N/A')") + if [[ ! "$PYZES_LOCATION" =~ "bindings/sysman/python/source" ]]; then + echo "❌ FAIL: pyzes is not loading from local source" + echo "Location: $PYZES_LOCATION" + exit 1 + fi + + # Check 2: Test silent import (no stdout output) + OUTPUT=$(python -c "import pyzes" 2>/dev/null) + if [ -n "$OUTPUT" ]; then + echo "❌ FAIL: Import produced stdout output:" + echo "$OUTPUT" + echo "" + echo "The 'import pyzes' statement should not print anything to stdout." + echo "Please remove any print statements from the module initialization." + exit 1 + fi + + # Check 3: Validate structure definitions match C headers + python test/validate_structures.py || { + echo "" + echo "Structure validation failed. Please ensure Python ctypes structures match the C headers." + exit 1 + } + + echo "✅ All validation checks passed: Local installation verified, import is silent, structures validated" diff --git a/bindings/sysman/python/test/validate_structures.py b/bindings/sysman/python/test/validate_structures.py new file mode 100644 index 00000000..264aa271 --- /dev/null +++ b/bindings/sysman/python/test/validate_structures.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +## +# Copyright (C) 2026 Intel Corporation +# +# SPDX-License-Identifier: MIT +# +## + +""" +Validates that Python ctypes structures in pyzes.py match the C structures +defined in the Level-Zero headers (include/zes_api.h, include/ze_api.h). +""" + +import re +import sys +from pathlib import Path +from typing import Dict, List, Tuple, Optional + +# Type mapping from C to Python ctypes +C_TO_PYTHON_TYPE_MAP = { + # Structure types + "ze_structure_type_t": "c_int32", + "zes_structure_type_t": "c_int32", + # Pointer types + "void*": "c_void_p", + "const void*": "c_void_p", + # Base C types + "char": "c_char", + "uint8_t": "c_ubyte", + "uint16_t": "c_uint16", + "uint32_t": "c_uint32", + "uint64_t": "c_uint64", + "int8_t": "c_int8", + "int16_t": "c_int16", + "int32_t": "c_int32", + "int64_t": "c_int64", + "float": "c_float", + "double": "c_double", +} + + +def parse_c_structure(header_content: str, struct_name: str) -> Optional[List[Tuple[str, str]]]: + """ + Parse a C structure definition from header content. + Returns list of (field_name, field_type) tuples. + """ + # Find the structure definition + pattern = rf"typedef\s+struct\s+_{struct_name}\s*\{{\s*(.*?)\}}\s*{struct_name}\s*;" + match = re.search(pattern, header_content, re.DOTALL) + + if not match: + return None + + struct_body = match.group(1) + fields = [] + + # Parse each field line + field_pattern = r"^\s*([a-zA-Z_][\w\s\*]+?)\s+([a-zA-Z_]\w+)(\[[^\]]+\])?\s*;.*?$" + + for line in struct_body.split('\n'): + # Skip comments and empty lines + line = re.sub(r'///.*$', '', line).strip() + if not line or line.startswith('//'): + continue + + match = re.match(field_pattern, line) + if match: + field_type = match.group(1).strip() + field_name = match.group(2).strip() + array_spec = match.group(3) + + # Construct the full type + if array_spec: + full_type = f"{field_type}{array_spec}" + else: + full_type = field_type + + fields.append((field_name, full_type)) + + return fields if fields else None + + +def parse_python_structure(pyzes_content: str, struct_name: str) -> Optional[List[Tuple[str, str]]]: + """ + Parse a Python ctypes structure definition from pyzes.py. + Returns list of (field_name, field_type) tuples. + """ + # Find the class definition + pattern = rf"class\s+{struct_name}\s*\(_PrintableStructure\):.*?_fields_\s*=\s*\[(.*?)\]" + match = re.search(pattern, pyzes_content, re.DOTALL) + + if not match: + return None + + fields_str = match.group(1) + fields = [] + + # Parse each field tuple + field_pattern = r'\(\s*"([^"]+)"\s*,\s*([^\)]+?)\s*\)' + + for field_match in re.finditer(field_pattern, fields_str): + field_name = field_match.group(1).strip() + field_type = field_match.group(2).strip().rstrip(',') + fields.append((field_name, field_type)) + + return fields if fields else None + + +def normalize_c_type(c_type: str) -> str: + """ + Convert C type to expected Python ctypes representation. + """ + c_type = c_type.strip() + + # Handle arrays: char[SIZE] -> c_char * SIZE + array_match = re.match(r'(\w+)\[([^\]]+)\]', c_type) + if array_match: + base_type = array_match.group(1) + size = array_match.group(2) + python_base = C_TO_PYTHON_TYPE_MAP.get(base_type, base_type) + return f"{python_base} * {size}" + + # Map type if in dictionary, otherwise keep as-is + return C_TO_PYTHON_TYPE_MAP.get(c_type, c_type) + + +def types_are_equivalent(type1: str, type2: str) -> bool: + """Check if two types are equivalent.""" + if type1 == type2: + return True + + # Check array types with same base and size + array_match1 = re.match(r'(\w+)\s*\*\s*(.+)', type1) + array_match2 = re.match(r'(\w+)\s*\*\s*(.+)', type2) + + if array_match1 and array_match2: + base1, size1 = array_match1.groups() + base2, size2 = array_match2.groups() + + if base1 == base2 and size1 == size2: + return True + + return False + + +def compare_structures(c_fields: List[Tuple[str, str]], + py_fields: List[Tuple[str, str]], + struct_name: str) -> List[str]: + """ + Compare C and Python structure fields. + Returns list of error messages (empty if structures match). + """ + errors = [] + + if len(c_fields) != len(py_fields): + errors.append( + f"Field count mismatch: C has {len(c_fields)} fields, Python has {len(py_fields)} fields" + ) + + # Compare field by field + max_len = max(len(c_fields), len(py_fields)) + for i in range(max_len): + if i >= len(c_fields): + py_name, py_type = py_fields[i] + errors.append(f"Extra field in Python: ({py_name}, {py_type})") + continue + + if i >= len(py_fields): + c_name, c_type = c_fields[i] + errors.append(f"Missing field in Python: ({c_name}, {c_type})") + continue + + c_name, c_type = c_fields[i] + py_name, py_type = py_fields[i] + + # Check field name + if c_name != py_name: + errors.append( + f"Field {i}: name mismatch - C: '{c_name}' ({c_type}), Python: '{py_name}' ({py_type})" + ) + # Skip type comparison when names don't match - they're different fields + continue + + # Check field type (only if names match) + expected_type = normalize_c_type(c_type) + if not types_are_equivalent(expected_type, py_type): + errors.append( + f"Field '{c_name}' (position {i}): type mismatch - C: '{c_type}' -> '{expected_type}', Python: '{py_type}'" + ) + + return errors + + +def get_all_structures_from_pyzes(pyzes_content: str) -> List[str]: + """ + Extract all structure names that inherit from _PrintableStructure in pyzes.py. + Returns list of structure names. + """ + structures = [] + pattern = r'^class\s+(\w+)\(_PrintableStructure\):' + + for match in re.finditer(pattern, pyzes_content, re.MULTILINE): + struct_name = match.group(1) + structures.append(struct_name) + + return structures + + +def main(): + """Main validation function.""" + script_dir = Path(__file__).parent + repo_root = script_dir.parent.parent.parent.parent + + # Paths + zes_header = repo_root / "include" / "zes_api.h" + ze_header = repo_root / "include" / "ze_api.h" + pyzes_file = repo_root / "bindings" / "sysman" / "python" / "source" / "pyzes.py" + + # Check files exist + for file_path in [zes_header, ze_header, pyzes_file]: + if not file_path.exists(): + print(f"❌ FAIL: Required file not found: {file_path}") + return 1 + + # Read files + zes_content = zes_header.read_text() + ze_content = ze_header.read_text() + pyzes_content = pyzes_file.read_text() + + # Dynamically discover all structures from pyzes.py + structures_to_check = get_all_structures_from_pyzes(pyzes_content) + + if not structures_to_check: + print("❌ FAIL: No structures found in pyzes.py") + return 1 + + print(f"Found {len(structures_to_check)} structures in pyzes.py") + + all_errors = {} + structures_checked = 0 + structures_not_in_headers = [] + + for struct_name in structures_to_check: + # Parse from C header + c_fields = parse_c_structure(zes_content, struct_name) + if c_fields is None: + # Try ze_api.h for core structures + c_fields = parse_c_structure(ze_content, struct_name) + + if c_fields is None: + structures_not_in_headers.append(struct_name) + continue + + # Parse from Python + py_fields = parse_python_structure(pyzes_content, struct_name) + if py_fields is None: + print(f"❌ FAIL: Structure '{struct_name}' not found in pyzes.py") + all_errors[struct_name] = ["Structure not found in pyzes.py"] + continue + + # Compare + errors = compare_structures(c_fields, py_fields, struct_name) + if errors: + all_errors[struct_name] = errors + else: + structures_checked += 1 + + # Report results + print() + print(f"{'='*70}") + print(f"Structure Validation Summary") + print(f"{'='*70}") + print(f"Total structures found in pyzes.py: {len(structures_to_check)}") + print(f"Structures validated successfully: {structures_checked}") + + if structures_not_in_headers: + print(f"Structures not found in C headers: {len(structures_not_in_headers)}") + for struct_name in structures_not_in_headers: + print(f" • {struct_name}") + + if all_errors: + print(f"Structures with mismatches: {len(all_errors)}") + print(f"{'='*70}") + print() + print(f"❌ FAIL: {len(all_errors)} structure(s) have validation errors:\n") + for struct_name, errors in all_errors.items(): + print(f"Structure: {struct_name}") + for error in errors: + print(f" • {error}") + print() + return 1 + else: + print(f"Structures with mismatches: 0") + print(f"{'='*70}") + print() + print(f"✅ All structure validations passed!") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8f924ee2115685acb728bdaa85cd12eff02570a3 Mon Sep 17 00:00:00 2001 From: shubham kumar Date: Tue, 2 Jun 2026 15:59:50 +0000 Subject: [PATCH 2/4] test: Add new checkers for pyzes Related-To: NEO-18718 Signed-off-by: shubham kumar --- .../sysman/python/test/validate_structures.py | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/bindings/sysman/python/test/validate_structures.py b/bindings/sysman/python/test/validate_structures.py index 264aa271..548b9624 100644 --- a/bindings/sysman/python/test/validate_structures.py +++ b/bindings/sysman/python/test/validate_structures.py @@ -14,7 +14,7 @@ import re import sys from pathlib import Path -from typing import Dict, List, Tuple, Optional +from typing import List, Optional, Tuple # Type mapping from C to Python ctypes C_TO_PYTHON_TYPE_MAP = { @@ -39,7 +39,9 @@ } -def parse_c_structure(header_content: str, struct_name: str) -> Optional[List[Tuple[str, str]]]: +def parse_c_structure( + header_content: str, struct_name: str +) -> Optional[List[Tuple[str, str]]]: """ Parse a C structure definition from header content. Returns list of (field_name, field_type) tuples. @@ -47,62 +49,66 @@ def parse_c_structure(header_content: str, struct_name: str) -> Optional[List[Tu # Find the structure definition pattern = rf"typedef\s+struct\s+_{struct_name}\s*\{{\s*(.*?)\}}\s*{struct_name}\s*;" match = re.search(pattern, header_content, re.DOTALL) - + if not match: return None - + struct_body = match.group(1) fields = [] - + # Parse each field line field_pattern = r"^\s*([a-zA-Z_][\w\s\*]+?)\s+([a-zA-Z_]\w+)(\[[^\]]+\])?\s*;.*?$" - - for line in struct_body.split('\n'): + + for line in struct_body.split("\n"): # Skip comments and empty lines - line = re.sub(r'///.*$', '', line).strip() - if not line or line.startswith('//'): + line = re.sub(r"///.*$", "", line).strip() + if not line or line.startswith("//"): continue - + match = re.match(field_pattern, line) if match: field_type = match.group(1).strip() field_name = match.group(2).strip() array_spec = match.group(3) - + # Construct the full type if array_spec: full_type = f"{field_type}{array_spec}" else: full_type = field_type - + fields.append((field_name, full_type)) - + return fields if fields else None -def parse_python_structure(pyzes_content: str, struct_name: str) -> Optional[List[Tuple[str, str]]]: +def parse_python_structure( + pyzes_content: str, struct_name: str +) -> Optional[List[Tuple[str, str]]]: """ Parse a Python ctypes structure definition from pyzes.py. Returns list of (field_name, field_type) tuples. """ # Find the class definition - pattern = rf"class\s+{struct_name}\s*\(_PrintableStructure\):.*?_fields_\s*=\s*\[(.*?)\]" + pattern = ( + rf"class\s+{struct_name}\s*\(_PrintableStructure\):.*?_fields_\s*=\s*\[(.*?)\]" + ) match = re.search(pattern, pyzes_content, re.DOTALL) - + if not match: return None - + fields_str = match.group(1) fields = [] - + # Parse each field tuple field_pattern = r'\(\s*"([^"]+)"\s*,\s*([^\)]+?)\s*\)' - + for field_match in re.finditer(field_pattern, fields_str): field_name = field_match.group(1).strip() - field_type = field_match.group(2).strip().rstrip(',') + field_type = field_match.group(2).strip().rstrip(",") fields.append((field_name, field_type)) - + return fields if fields else None @@ -111,15 +117,15 @@ def normalize_c_type(c_type: str) -> str: Convert C type to expected Python ctypes representation. """ c_type = c_type.strip() - + # Handle arrays: char[SIZE] -> c_char * SIZE - array_match = re.match(r'(\w+)\[([^\]]+)\]', c_type) + array_match = re.match(r"(\w+)\[([^\]]+)\]", c_type) if array_match: base_type = array_match.group(1) size = array_match.group(2) python_base = C_TO_PYTHON_TYPE_MAP.get(base_type, base_type) return f"{python_base} * {size}" - + # Map type if in dictionary, otherwise keep as-is return C_TO_PYTHON_TYPE_MAP.get(c_type, c_type) @@ -128,35 +134,35 @@ def types_are_equivalent(type1: str, type2: str) -> bool: """Check if two types are equivalent.""" if type1 == type2: return True - + # Check array types with same base and size - array_match1 = re.match(r'(\w+)\s*\*\s*(.+)', type1) - array_match2 = re.match(r'(\w+)\s*\*\s*(.+)', type2) - + array_match1 = re.match(r"(\w+)\s*\*\s*(.+)", type1) + array_match2 = re.match(r"(\w+)\s*\*\s*(.+)", type2) + if array_match1 and array_match2: base1, size1 = array_match1.groups() base2, size2 = array_match2.groups() - + if base1 == base2 and size1 == size2: return True - + return False -def compare_structures(c_fields: List[Tuple[str, str]], - py_fields: List[Tuple[str, str]], - struct_name: str) -> List[str]: +def compare_structures( + c_fields: List[Tuple[str, str]], py_fields: List[Tuple[str, str]], struct_name: str +) -> List[str]: """ Compare C and Python structure fields. Returns list of error messages (empty if structures match). """ errors = [] - + if len(c_fields) != len(py_fields): errors.append( f"Field count mismatch: C has {len(c_fields)} fields, Python has {len(py_fields)} fields" ) - + # Compare field by field max_len = max(len(c_fields), len(py_fields)) for i in range(max_len): @@ -164,15 +170,15 @@ def compare_structures(c_fields: List[Tuple[str, str]], py_name, py_type = py_fields[i] errors.append(f"Extra field in Python: ({py_name}, {py_type})") continue - + if i >= len(py_fields): c_name, c_type = c_fields[i] errors.append(f"Missing field in Python: ({c_name}, {c_type})") continue - + c_name, c_type = c_fields[i] py_name, py_type = py_fields[i] - + # Check field name if c_name != py_name: errors.append( @@ -180,14 +186,14 @@ def compare_structures(c_fields: List[Tuple[str, str]], ) # Skip type comparison when names don't match - they're different fields continue - + # Check field type (only if names match) expected_type = normalize_c_type(c_type) if not types_are_equivalent(expected_type, py_type): errors.append( f"Field '{c_name}' (position {i}): type mismatch - C: '{c_type}' -> '{expected_type}', Python: '{py_type}'" ) - + return errors @@ -197,12 +203,12 @@ def get_all_structures_from_pyzes(pyzes_content: str) -> List[str]: Returns list of structure names. """ structures = [] - pattern = r'^class\s+(\w+)\(_PrintableStructure\):' - + pattern = r"^class\s+(\w+)\(_PrintableStructure\):" + for match in re.finditer(pattern, pyzes_content, re.MULTILINE): struct_name = match.group(1) structures.append(struct_name) - + return structures @@ -210,77 +216,77 @@ def main(): """Main validation function.""" script_dir = Path(__file__).parent repo_root = script_dir.parent.parent.parent.parent - + # Paths zes_header = repo_root / "include" / "zes_api.h" ze_header = repo_root / "include" / "ze_api.h" pyzes_file = repo_root / "bindings" / "sysman" / "python" / "source" / "pyzes.py" - + # Check files exist for file_path in [zes_header, ze_header, pyzes_file]: if not file_path.exists(): print(f"❌ FAIL: Required file not found: {file_path}") return 1 - + # Read files zes_content = zes_header.read_text() ze_content = ze_header.read_text() pyzes_content = pyzes_file.read_text() - + # Dynamically discover all structures from pyzes.py structures_to_check = get_all_structures_from_pyzes(pyzes_content) - + if not structures_to_check: print("❌ FAIL: No structures found in pyzes.py") return 1 - + print(f"Found {len(structures_to_check)} structures in pyzes.py") - + all_errors = {} structures_checked = 0 structures_not_in_headers = [] - + for struct_name in structures_to_check: # Parse from C header c_fields = parse_c_structure(zes_content, struct_name) if c_fields is None: # Try ze_api.h for core structures c_fields = parse_c_structure(ze_content, struct_name) - + if c_fields is None: structures_not_in_headers.append(struct_name) continue - + # Parse from Python py_fields = parse_python_structure(pyzes_content, struct_name) if py_fields is None: print(f"❌ FAIL: Structure '{struct_name}' not found in pyzes.py") all_errors[struct_name] = ["Structure not found in pyzes.py"] continue - + # Compare errors = compare_structures(c_fields, py_fields, struct_name) if errors: all_errors[struct_name] = errors else: structures_checked += 1 - + # Report results print() - print(f"{'='*70}") - print(f"Structure Validation Summary") - print(f"{'='*70}") + print("=" * 70) + print("Structure Validation Summary") + print("=" * 70) print(f"Total structures found in pyzes.py: {len(structures_to_check)}") print(f"Structures validated successfully: {structures_checked}") - + if structures_not_in_headers: print(f"Structures not found in C headers: {len(structures_not_in_headers)}") for struct_name in structures_not_in_headers: print(f" • {struct_name}") - + if all_errors: print(f"Structures with mismatches: {len(all_errors)}") - print(f"{'='*70}") + print("=" * 70) print() print(f"❌ FAIL: {len(all_errors)} structure(s) have validation errors:\n") for struct_name, errors in all_errors.items(): @@ -290,10 +296,10 @@ def main(): print() return 1 else: - print(f"Structures with mismatches: 0") - print(f"{'='*70}") + print("Structures with mismatches: 0") + print("=" * 70) print() - print(f"✅ All structure validations passed!") + print("✅ All structure validations passed!") return 0 From 5178e4cd3ac37f40c0bbe2f6a0da640b437ca0d2 Mon Sep 17 00:00:00 2001 From: shubham kumar Date: Tue, 2 Jun 2026 16:10:15 +0000 Subject: [PATCH 3/4] test: Add new checkers for pyzes Related-To: NEO-18718 Signed-off-by: shubham kumar --- .../workflows/bindings-sysman-python-checks.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/bindings-sysman-python-checks.yml b/.github/workflows/bindings-sysman-python-checks.yml index effcc6e3..f161b6ea 100644 --- a/.github/workflows/bindings-sysman-python-checks.yml +++ b/.github/workflows/bindings-sysman-python-checks.yml @@ -44,6 +44,21 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies for Level-Zero loader build + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Build and install Level-Zero loader + working-directory: ${{ github.workspace }} + run: | + mkdir -p build + cd build + cmake .. -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr + cmake --build . --parallel $(nproc) + sudo cmake --build . --target install + sudo ldconfig + - name: Create virtual environment and install run: | python -m venv .venv From b4e081eb6d845f3dd325ced49afa02d6d2696071 Mon Sep 17 00:00:00 2001 From: shubham kumar Date: Tue, 2 Jun 2026 16:10:15 +0000 Subject: [PATCH 4/4] test: Add new checkers for pyzes Related-To: NEO-18718 Signed-off-by: shubham kumar