Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI/CD and implement the first two rules #9

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
**Description**

A brief description of the PR.

**Main changes**

1. First change
2. Second change

**How was the PR tested?**

1. Unit-test with some sample data. All unit tests passed.
2. Print output values. The printed outputs look reasonable.
3. Executed container locally and it worked.

**Notes**
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Tests
on: push
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
os: [ubuntu-22.04, windows-2019]
name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- run: pip install -r requirements.txt -r requirements-tests.txt
- run: python -m pytest -vv
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,20 @@
**/report.json
**/report.xqar
**/esmini
**/reports
**/reports

.vscode
*.pyc
__pycache__
.venv*
env
dist
.mypy_cache
.idea
site
.coverage
htmlcov
.pytest_cache
coverage.xml
.coverage*
.python-version
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: local
hooks:
- id: black
name: black
entry: black
language: system
types: [python]
98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,97 @@
# qc-openscenarioxml
# qc-openscenarioxml

This project implements the OpenScenario Checker for the ASAM Quality Checker project.

## Installation

To install the project, run:

```
pip install -r requirements.txt
```

This will install the needed dependencies to your local Python.

## Usage

The checker can be used as a Python script:

```
python main.py --help
usage: QC OpenScenario Checker [-h] (-d | -c CONFIG_PATH)
This is a collection of scripts for checking validity of OpenScenario (.xosc) files.
options:
-h, --help show this help message and exit
-d, --default_config
-c CONFIG_PATH, --config_path CONFIG_PATH
```

### Example

- No issues found

```
python3 main.py -c example_config.xml
2024-06-12 15:14:11,864 - Initializing checks
2024-06-12 15:14:11,865 - Executing xml checks
2024-06-12 15:14:11,865 - Executing is_an_xml_document check
asam.net:xosc:0.9.0:is_an_xml_document
2024-06-12 15:14:11,865 - Issues found - 0
2024-06-12 15:14:11,865 - Done
```


- Issues found on file

```
python3 main.py -c example_config.xml
2024-06-12 15:19:45,139 - Initializing checks
2024-06-12 15:19:45,140 - Executing xml checks
2024-06-12 15:19:45,140 - Executing is_an_xml_document check
asam.net:xosc:0.9.0:is_an_xml_document
2024-06-12 15:19:45,140 - Issues found - 1
2024-06-12 15:19:45,141 - Done

```


## Tests

To run the tests, you need to have installed the main dependencies mentioned
at [Installation](#installation).

Install Python tests and development dependencies:

```
pip install -r requirements-tests.txt
```

Execute tests:

```
python -m pytest -vv
```

They should output something similar to:

```
===================== test session starts =====================
platform linux -- Python 3.11.9, pytest-8.2.2, pluggy-1.5.0
```

You can check more options for pytest at its [own documentation](https://docs.pytest.org/).

## Contributing

For contributing, you need to install the development requirements besides the
test and installation requirements, for that run:

```
pip install -r requirements-dev.txt
```

You need to have pre-commit installed and install the hooks:

```
pre-commit install
```
64 changes: 64 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import argparse
import logging
from datetime import datetime

from qc_baselib import Configuration, Result

from qc_openscenario import constants
from qc_openscenario.checks.schema_checker import schema_checker
from qc_openscenario.checks.basic_checker import basic_checker

logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)


def args_entrypoint() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="QC OpenScenario Checker",
description="This is a collection of scripts for checking validity of OpenScenario (.xosc) files.",
)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-d", "--default_config", action="store_true")
group.add_argument("-c", "--config_path")

return parser.parse_args()


def main():
args = args_entrypoint()

logging.info("Initializing checks")

if args.default_config:
raise RuntimeError("Not implemented.")
else:
config = Configuration()
config.load_from_file(xml_file_path=args.config_path)

result = Result()
result.register_checker_bundle(
name=constants.BUNDLE_NAME,
build_date=datetime.today().strftime("%Y-%m-%d"),
description="OpenScenario checker bundle",
version=constants.BUNDLE_VERSION,
summary="",
)
result.set_result_version(version=constants.BUNDLE_VERSION)

# 1. Run basic checks
checker_data = basic_checker.run_checks(config=config, result=result)

# 2. Run xml checks
schema_checker.run_checks(checker_data)

result.write_to_file(
config.get_checker_bundle_param(
checker_bundle_name=constants.BUNDLE_NAME, param_name="resultFile"
)
)

logging.info("Done")


if __name__ == "__main__":
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions qc_openscenario/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import constants as constants
from . import checks as checks
3 changes: 3 additions & 0 deletions qc_openscenario/checks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import schema_checker as schema_checker
from . import basic_checker as basic_checker
from . import models as models
3 changes: 3 additions & 0 deletions qc_openscenario/checks/basic_checker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import basic_constants as basic_constants
from . import basic_checker as basic_checker
from . import valid_xml_document as valid_xml_document
62 changes: 62 additions & 0 deletions qc_openscenario/checks/basic_checker/basic_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging

from lxml import etree

from qc_baselib import Configuration, Result, StatusType

from qc_openscenario import constants
from qc_openscenario.checks import utils, models

from qc_openscenario.checks.basic_checker import (
basic_constants,
valid_xml_document,
)


def run_checks(config: Configuration, result: Result) -> models.CheckerData:
logging.info("Executing basic checks")

result.register_checker(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=basic_constants.CHECKER_ID,
description="Check if basic properties of input file are properly set",
summary="",
)

xml_file_path = config.get_config_param("XoscFile")
is_xml = valid_xml_document.check_rule(xml_file_path, result)

checker_data = None

if not is_xml:
logging.error("Error in input xml!")
checker_data = models.CheckerData(
input_file_xml_root=None,
config=config,
result=result,
schema_version=None,
)

else:
root = etree.parse(config.get_config_param("XoscFile"))
xosc_schema_version = utils.get_standard_schema_version(root)

checker_data = models.CheckerData(
input_file_xml_root=root,
config=config,
result=result,
schema_version=xosc_schema_version,
)

logging.info(
f"Issues found - {result.get_checker_issue_count(checker_bundle_name=constants.BUNDLE_NAME, checker_id=basic_constants.CHECKER_ID)}"
)

# TODO: Add logic to deal with error or to skip it
result.set_checker_status(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=basic_constants.CHECKER_ID,
status=StatusType.COMPLETED,
)

return checker_data
1 change: 1 addition & 0 deletions qc_openscenario/checks/basic_checker/basic_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CHECKER_ID = "basic_xosc"
71 changes: 71 additions & 0 deletions qc_openscenario/checks/basic_checker/valid_xml_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import logging

from dataclasses import dataclass
from typing import List

from lxml import etree

from qc_baselib import Configuration, Result, IssueSeverity

from qc_openscenario import constants
from qc_openscenario.checks import utils, models

from qc_openscenario.checks.basic_checker import basic_constants


def _is_xml_doc(file_path: str) -> tuple[bool, tuple[int, int]]:
try:
with open(file_path, "rb") as file:
xml_content = file.read()
etree.fromstring(xml_content)
logging.info("- It is an xml document.")
return True, None
except etree.XMLSyntaxError as e:
logging.error(f"- Error: {e}")
logging.error(f"- Error occurred at line {e.lineno}, column {e.offset}")
return False, (e.lineno, e.offset)


def check_rule(input_xml_file_path: str, result: Result) -> bool:
"""
Implements a rule to check if input file is a valid xml document

More info at
- https://github.com/asam-ev/qc-openscenarioxml/issues/1
"""
logging.info("Executing valid_xml_document check")

rule_uid = result.register_rule(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=basic_constants.CHECKER_ID,
emanating_entity="asam.net",
standard="xosc",
definition_setting="1.0.0",
rule_full_name="xml.valid_xml_document",
)

is_valid, error_location = _is_xml_doc(input_xml_file_path)

if not is_valid:

issue_id = result.register_issue(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=basic_constants.CHECKER_ID,
description="Issue flagging when input file is not a valid xml document",
level=IssueSeverity.ERROR,
rule_uid=rule_uid,
)

result.add_file_location(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=basic_constants.CHECKER_ID,
issue_id=issue_id,
row=error_location[0],
column=error_location[1],
file_type="xosc",
description=f"Invalid xml detected",
)

return False

return True
12 changes: 12 additions & 0 deletions qc_openscenario/checks/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dataclasses import dataclass
from lxml import etree

from qc_baselib import Configuration, Result


@dataclass
class CheckerData:
input_file_xml_root: etree._ElementTree
config: Configuration
result: Result
schema_version: str
Loading