Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
31 changes: 26 additions & 5 deletions tmt/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import tmt.utils
import tmt.utils.git
import tmt.utils.jira
import tmt.utils.templates
from tmt._compat.typing import Self
from tmt.checks import Check
from tmt.container import (
Expand Down Expand Up @@ -390,6 +391,14 @@ def resolve_dynamic_ref(self, git_repository: Path, plan: 'Plan') -> None:
)


#: A template showing changes made by an ``adjust`` rule.
NODE_ADJUST_DIFF_TEMPLATE = """
{{ RULE | trim }}

{{ DIFF | join('\n') }}
"""


def create_adjust_callback(logger: tmt.log.Logger) -> fmf.base.AdjustCallback:
"""
Create a custom callback for fmf's adjust.
Expand All @@ -399,10 +408,15 @@ def create_adjust_callback(logger: tmt.log.Logger) -> fmf.base.AdjustCallback:
a callback closure with the given logger.
"""

def callback(node: fmf.Tree, rule: _RawAdjustRule, applied: Optional[bool]) -> None:
def callback(
node: fmf.Tree,
rule: _RawAdjustRule,
applied: Optional[bool],
before: Optional[fmf.Tree] = None,
) -> None:
if applied is None:
logger.verbose(
f"Adjust rule skipped on '{node.name}'",
f"fmf node '{node.name}' skipped by adjust rule, condition undecided",
tmt.utils.format_value(rule, key_color='cyan'),
color='blue',
level=3,
Expand All @@ -411,17 +425,24 @@ def callback(node: fmf.Tree, rule: _RawAdjustRule, applied: Optional[bool]) -> N

elif applied is False:
logger.verbose(
f"Adjust rule not applied to '{node.name}'",
f"fmf node '{node.name}' not modified by adjust rule",
tmt.utils.format_value(rule, key_color='cyan'),
color='red',
level=3,
topic=tmt.log.Topic.ADJUST_DECISIONS,
)

else:
assert before is not None

logger.verbose(
f"Adjust rule applied to '{node.name}'",
tmt.utils.format_value(rule, key_color='cyan'),
f"fmf node '{node.name}' modified by adjust rule",
tmt.utils.templates.render_diff(
NODE_ADJUST_DIFF_TEMPLATE,
before.data,
node.data,
RULE=tmt.utils.format_value(rule, key_color='cyan'),
),
color='green',
level=3,
topic=tmt.log.Topic.ADJUST_DECISIONS,
Expand Down
13 changes: 6 additions & 7 deletions tmt/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from tmt.container import Extra, MetadataContainer, metadata_field
from tmt.log import Logger, Topic
from tmt.utils import FieldValueSource, Path, ShellScript
from tmt.utils.templates import render_template
from tmt.utils.templates import render_diff, render_template

if TYPE_CHECKING:
from tmt.base import Core, Test
Expand All @@ -17,8 +17,7 @@

#: A template showing changes made by an instruction.
KEY_DIFF_TEMPLATE = """
{{ OLD_VALUE | to_yaml | prefix('- ') | style(fg='red') | trim }}
{{ NEW_VALUE | to_yaml | prefix('+ ') | style(fg='green') | trim }}
{{ DIFF | join('\n') }}

Field value source changed from {{ OLD_VALUE_SOURCE.value | style(fg='red') }} to {{ NEW_VALUE_SOURCE.value | style(fg='green') }}
""" # noqa: E501
Expand Down Expand Up @@ -128,11 +127,11 @@ def set_key(
current_value_source = obj._field_value_sources[key] = FieldValueSource.POLICY

logger.info(
f"Modified '{obj.name}'",
render_template(
f"fmf node '{obj.name}' modified by policy",
render_diff(
KEY_DIFF_TEMPLATE,
OLD_VALUE={key: old_value_exported},
NEW_VALUE={key: current_value_exported},
{key: old_value_exported},
{key: current_value_exported},
OLD_VALUE_SOURCE=old_value_source,
NEW_VALUE_SOURCE=current_value_source,
),
Expand Down
49 changes: 49 additions & 0 deletions tmt/utils/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
custom filters.
"""

import difflib
import re
import shlex
import textwrap
from collections.abc import Iterable
from re import Match
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -563,3 +565,50 @@ def render_template_file_into_file(
)

output_filepath.append_text('\n')


def render_diff(
template: str,
left: Any,
right: Any,
**variables: Any,
) -> str:
"""
Render a template showing diff between two data structures.

Both objects are turned into their YAML representation, and then
both representations are compared for lines missing and added:

* lines unique to the ``left`` object will be prefixed with a
``-`` string, and "negatively" colorized (red by default).
* lines unique to the ``right`` object will be prefixed with a
``+`` string, and "positively" colorized (green by default).

:param template: template to render. Variable ``DIFF`` will contain
colorized lines of the diff.
:param left: object on the left side of the comparison.
:param right: object on the right side of the comparison
"""

from tmt.utils.themes import style

def _render_diff() -> Iterable[str]:
"""
Compare two objects and render "missing" and "added" lines.
"""

for line in difflib.ndiff(
dict_to_yaml(left).splitlines(), dict_to_yaml(right).splitlines()
):
# See https://docs.python.org/3/library/difflib.html#difflib.Differ for "markers"
# at beginnings of lines

# '- ' - unique in "before"
if line.startswith('- '):
yield style(line, fg='red')

# '+ ' - unique in "after"
elif line.startswith('+ '):
yield style(line, fg='green')

return render_template(template, DIFF=_render_diff(), **variables)
Loading