Commit d47a866d authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch '7-fix-pydantic-parse_obj-deprecation-warning' into 'main'

Skip partial job definition and add explicit output messages for each job

See merge request to-be-continuous/tools/tbc-check!8
parents a58224d1 f95a8219
Loading
Loading
Loading
Loading
+25 −5
Original line number Diff line number Diff line
@@ -468,12 +468,15 @@ def _check_job(job_name: str, tpl_body, job_prefix) -> int:


def _check_inheritance(tpl_body: dict[str, Any]) -> int:
    err_count = 0

    # Gather actual jobs by filtering non-job items in template
    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
    # Count non-hidden jobs to infer if it's a single job template or not
    is_single_job_tpl = sum(1 for name in job_bodies
               if not name.startswith(".")) == 1

@@ -484,23 +487,40 @@ def _check_inheritance(tpl_body: dict[str, Any]) -> int:
    #  - all jobs should inherit from a hidden base job
    #
    for name, body in iter(job_bodies.items()):
        job_err_count = 0
        # skip job with only partial overrides
        if not any(key in body for key in ["stage", "extends", "image"]):
            print(
                f"  {AnsiColors.HGRAY}✕ job '{name}' does not defines an actual job: skip{AnsiColors.RESET}"
            )
            continue
        base_job = body.get("extends", None)
        if is_single_job_tpl and base_job is not None:
            job_err_count += 1
            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:
            job_err_count += 1
            print(
                f"  {AnsiColors.RED}✕ multiple job template: job '{name}' should inherit a base job{AnsiColors.RESET}"
                f"  {AnsiColors.RED}✕ multiple job template: non-hidden job '{name}' should inherit a base job{AnsiColors.RESET}"
            )
        if base_job is not None and not base_job.startswith("."):
            job_err_count += 1
            print(
                f"  {AnsiColors.RED}multiple job template: job '{name}' inherits from non-hidden job '{base_job}'{AnsiColors.RESET}"
                f"  {AnsiColors.RED}✕ 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}"
                f"  {AnsiColors.YELLOW}⚠ job '{name}' inherits from '{base_job}' which is not defined in current template{AnsiColors.RESET}"
            )
        if job_err_count == 0:
            print(
                f"  {AnsiColors.GREEN}{AnsiColors.RESET} job '{name}' inheritance: OK"
            )
        err_count += job_err_count

    return err_count

def _check_tpl(
    kicker: dict[str, any],
@@ -529,7 +549,7 @@ def _check_tpl(

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

    # check jobs
    # ----------
+61 −56
Original line number Diff line number Diff line
from copy import deepcopy

import pytest

from tbc_check import checker


def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest.CaptureFixture[str]):
    tpl_body = {
TPL_MULTIPLE_JOB = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".base-job": {},
        ".base-job": { "image": "base-image" },
        "prefix-job1": {
            "extends": ".base-job"
            "stage": "build",
            "extends": ".base-job",
        },
        "prefix-job2": {
            "extends": ".other-base-job"
            "stage": "test",
            "extends": ".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 = {
TPL_SINGLE_JOB = {
        "workflow": None,
        "variables": None,
        "stages": None,
        ".base-job": {},
        "prefix-job1": {
            "extends": ".base-job"
            "stage": "build",
        },
        "prefix-job2": {},
    }

def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest.CaptureFixture[str]):
    tpl_body = deepcopy(TPL_MULTIPLE_JOB)
    tpl_body["prefix-job2"]["extends"] = ".undefined"

    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"
        "  \x1b[0;32m✓\x1b[0m job '.base-job' inheritance: OK\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n"
        "  \x1b[0;33m⚠ job 'prefix-job2' inherits from '.undefined' which is not defined in current template\x1b[0m\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job2' inheritance: OK\n"
    )

def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFixture[str]):
    tpl_body = deepcopy(TPL_MULTIPLE_JOB)
    tpl_body["prefix-job2"].pop("extends")

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;32m✓\x1b[0m job '.base-job' inheritance: OK\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n"
        "  \x1b[0;31m✕ multiple job template: non-hidden 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"
        },
    }
    tpl_body = deepcopy(TPL_MULTIPLE_JOB)

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        ""
        "  \x1b[0;32m✓\x1b[0m job '.base-job' inheritance: OK\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job2' inheritance: OK\n"
    )


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"
        },
    }
    tpl_body = deepcopy(TPL_MULTIPLE_JOB)
    tpl_body["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"
        "  \x1b[0;32m✓\x1b[0m job '.base-job' inheritance: OK\n"
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n"
        "  \x1b[0;31m✕ job 'prefix-job2' inherits from non-hidden job 'prefix-job1'\x1b[0m\n"
    )


def test_check_inheritance_single_job_without_inheritance(capfd: pytest.CaptureFixture[str]):
    tpl_body = deepcopy(TPL_SINGLE_JOB)

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\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"
        },
    }
    tpl_body = deepcopy(TPL_SINGLE_JOB)
    tpl_body["prefix-job1"]["extends"] = ".base-job"

    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"
        "  \x1b[0;33m⚠ job 'prefix-job1' inherits from '.base-job' which is not defined in current template\x1b[0m\n"
    )

def test_check_inheritance_single_job_override_only(capfd: pytest.CaptureFixture[str]):
    tpl_body = deepcopy(TPL_SINGLE_JOB)
    tpl_body["prefix-job1"] = { "rules": {} }

    res = checker._check_inheritance(tpl_body=tpl_body)
    out, err = capfd.readouterr()
    assert out == (
        "  \x1b[90m✕ job 'prefix-job1' does not defines an actual job: skip\x1b[0m\n"
    )