Repository: teemtee/fmf

Issue:
Regression: No longer possible to extend dictionary inside adjust block (+ sign is stripped) 
Expectation when running `tmt -c dimension=value tests show` for this test metadata is to have environment containing both 'FOO' and 'BAR' values. 

However fmf-1.5.0+ makes tmt to get only the 'BAR' value.

Reproducer metadata:
```
test: echo
environment+:
  FOO: bar

adjust+:
  when: dimension == value
  environment+:
    BAR: baz
```

This seems to be caused by 'fmf' to report different 'raw' data:

fmf-1.4.0 shows **environment+** inside adjust key
```
$ fmf_14 show
/repro
adjust: {'environment+': {'BAR': 'baz'}, 'when': 'dimension == value'}
environment: {'FOO': 'bar'}
test: echo
```

While recent (1.5.0 and 1.6.0) show **environment:** instead
```
$ fmf_16 show
/repro
adjust: {'environment': {'BAR': 'baz'}, 'when': 'dimension == value'}
environment: {'FOO': 'bar'}
test: echo
```

Discussion:
@lukaszachy: bisect shows the regression happened in 759788fa45b4f621eb706588bcd990fcbccb330e@lukaszachy: Decision: Revert #263, document that #190 is not possible in current fmf implementation.
To be released ASAP in 1.6.1 @lukaszachy: Hm, if the reproducer is stored in 'main.fmf' and thus producing node named '/' then it  contains 'adjust+' and 'environment+' keys. However if metadata cause named node (eg '/repro') by being in 'repro.fmf' file, or using '/repro:' in key, the node contains 'adjust' and 'environment'.

```
$ fmf show
/
adjust+: {'environment+': {'BAR': 'baz'}}
environment+: {'FOO': 'bar'}

$ mv main.fmf repro.fmf
$ fmf show
/repro
adjust: {'environment+': {'BAR': 'baz'}}
environment: {'FOO': 'bar'}
```



Solution patch:
diff --git a/docs/features.rst b/docs/features.rst
index 0535a5b..01cbeeb 100644
--- a/docs/features.rst
+++ b/docs/features.rst
@@ -98,6 +98,9 @@ or when merging a dictionary with a list.
 Exception ``MergeError`` is raised if types are not compatible. When
 the ``+`` suffix is applied on dictionaries ``update()`` method is
 used to merge content of given dictionary instead of replacing it.
+When such parent dictionary doesn't exist, but the suffix ``+`` is used
+at the top level, the dictionary is created and named without the suffix.
+However, for dictionaries at deeper levels, the suffix remains in their name.
 
 Example: Merging dictionary with a list::
 
@@ -159,6 +162,25 @@ results in::
             url: https://github.com/project2
             filter: "tier:2"
 
+Example: Merging with not yet defined dictionaries::
+
+    environment+:
+        CLEAR: "1"
+    adjust+:
+        when: distro == fedora
+        environment+:
+            FEDORA: "1"
+
+results in (no ``+`` suffix in ``adjust`` and top ``environment`` keys)::
+
+    adjust:
+        when: distro == fedora
+        environment+:
+            FEDORA: "1"
+    environment:
+        CLEAR: "1"
+
+
 The special suffix ``+<`` can be used to prepend values instead of
 appending them. This might be handy when adjusting lists::
 
diff --git a/docs/releases.rst b/docs/releases.rst
index 83573f6..a1d1359 100644
--- a/docs/releases.rst
+++ b/docs/releases.rst
@@ -4,6 +4,14 @@
     Releases
 ======================
 
+fmf-1.6.1
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to allow adjust rules to merge dictionaries, nested
+dictionaries need to keep the suffix in their name. This behavior
+reverts to fmf-1.4.0 and is now documented in :ref:`merging<merging>`
+section.
+
 
 fmf-1.6.0
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/fmf/base.py b/fmf/base.py
index 6ad5863..a48b632 100644
--- a/fmf/base.py
+++ b/fmf/base.py
@@ -190,13 +190,7 @@ class Tree:
 
         # Set the value if key is not present
         if key not in data:
-            # Special handling for dictionaries as their keys might
-            # contain suffixes which should be removed
-            if isinstance(value, dict):
-                data[key] = {}
-                self._merge_special(data[key], value)
-            else:
-                data[key] = value
+            data[key] = value
             return
 
         # Parent is a list of dict and child is a dict


Test patch:
diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py
index 482209e..5248d96 100644
--- a/tests/unit/test_base.py
+++ b/tests/unit/test_base.py
@@ -4,6 +4,7 @@ import tempfile
 import threading
 import time
 from shutil import rmtree
+from textwrap import dedent
 
 import pytest
 from ruamel.yaml import YAML
@@ -158,6 +159,21 @@ class TestTree:
         assert 'because' not in child.data
         assert 'foo' in child.data
 
+    def test_adjust_can_extend(self):
+        """ It is possible to extend dictionary with adjust """
+        tree = Tree(YAML(typ='safe').load(dedent("""
+        /a:
+         environment+:
+          FOO: bar
+         adjust+:
+          environment+:
+            BAR: baz
+        """)))
+        tree.adjust(context=Context())
+        child = tree.find('/a')
+        assert child.data['environment']['FOO'] == "bar"
+        assert child.data['environment']['BAR'] == "baz"
+
     def test_deep_hierarchy(self):
         """ Deep hierarchy on one line """
         deep = Tree(EXAMPLES + "deep")
@@ -175,12 +191,12 @@ class TestTree:
         """ Extending undefined keys using '+' should work """
         deep = Tree(EXAMPLES + "deep")
         single = deep.find("/single")
-        assert single.get(["undefined", "deeper", "key"]) == "value"
+        assert single.get(["undefined", "deeper+", "key"]) == "value"
         child = deep.find("/parent/child")
         assert child.get("one") == 2
         assert child.get("two") == 4
         assert child.get("three") == 3
-        assert child.get(["undefined", "deeper", "key"]) == "value"
+        assert child.get(["undefined", "deeper+", "key"]) == "value"
 
     def test_merge_plus(self):
         """ Extending attributes using the '+' suffix """