Commit 0e19178c authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch '7-new-check-job-prototype-rule' into 'main'

Draft: feat: implement job-prototype and inheritance check

Closes #7

See merge request to-be-continuous/tools/tbc-check!7
parents 850eeb53 1effdd1e
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -467,6 +467,41 @@ def _check_job(job_name: str, tpl_body, job_prefix) -> int:
    return err_count


def _check_inheritance(tpl_body: dict[str, Any]) -> int:
    job_bodies = { name: body for name, body in tpl_body.items()
        if name not in ["stages", "workflow", "variables", "rules"]
           and isinstance(body, dict)
    }

    # count non-hidden jobs
    is_single_job_tpl = sum(1 for name in job_bodies
               if not name.startswith(".")) == 1

    #
    # Each template
    #  - should define only one job
    # Or
    #  - all jobs should inherit from a hidden base job
    #
    for name, body in iter(job_bodies.items()):
        base_job = body.get("extends", None)
        if is_single_job_tpl and base_job is not None:
            print(
                f"  {AnsiColors.RED}✕ single job template: job '{name}' inherits another job{AnsiColors.RESET}"
            )
        if not is_single_job_tpl and not name.startswith(".") and base_job is None:
            print(
                f"  {AnsiColors.RED}✕ multiple job template: job '{name}' should inherit a base job{AnsiColors.RESET}"
            )
        if base_job is not None and not base_job.startswith("."):
            print(
                f"  {AnsiColors.RED}✕ multiple job template: job '{name}' inherits from non-hidden job '{base_job}'{AnsiColors.RESET}"
            )
        if base_job is not None and base_job not in job_bodies:
            print(
                f"  {AnsiColors.YELLOW}⚠ multiple job template: job '{name}' inherits from '{base_job}' which is not defined in current template{AnsiColors.RESET}"
            )

def _check_tpl(
    kicker: dict[str, any],
    root_kicker: Optional[dict[str, any]],
@@ -492,6 +527,10 @@ def _check_tpl(
    inputs: dict[str, dict[str, any]] = dict(tpl_spec["spec"]["inputs"])
    err_count = 0

    # check inheritance
    # -----------------
    _check_inheritance(tpl_body=tpl_body)

    # check jobs
    # ----------
    for name, body in tpl_body.items():
+104 −0
Original line number Diff line number Diff line
import pytest

from tbc_check import checker


def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".base-job": {},
        "prefix-job1": {
            "extends": ".base-job"
        },
        "prefix-job2": {
            "extends": ".other-base-job"
        },
    }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;33m⚠ multiple job template: job 'prefix-job2' inherits from '.other-base-job' which is not defined in current template\x1b[0m\n"
    )

def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".base-job": {},
        "prefix-job1": {
            "extends": ".base-job"
        },
        "prefix-job2": {},
    }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;31m✕ multiple job template: job 'prefix-job2' should inherit a base job\x1b[0m\n"
    )

def test_check_inheritance_multiple_job_inheritance_good(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".hidden": None,
        ".base-job": {},
        "prefix-job1": {
            "extends": ".base-job"
        },
        "prefix-job2": {
            "extends": ".base-job"
        },
    }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        ""
    )


def test_check_inheritance_multiple_job_inheritance_from_non_hidden(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".hidden": None,
        ".base-job": {},
        "prefix-job1": {
            "extends": ".base-job"
        },
        "prefix-job2": {
            "extends": "prefix-job1"
        },
    }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;31m✕ multiple job template: job 'prefix-job2' inherits from non-hidden job 'prefix-job1'\x1b[0m\n"
    )


def test_check_inheritance_single_job_with_inheritance(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".hidden": None,
        ".prefix-base": {},
        "prefix-job1": {
            "extends": ".prefix-base"
        },
    }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;31m✕ single job template: job 'prefix-job1' inherits another job\x1b[0m\n"
    )