From a1d4f91d1a287b072a48589712d233ea879388dd Mon Sep 17 00:00:00 2001
From: Vsevolod Fedorov <vsevolod.fedorov@gmail.com>
Date: Wed, 15 Nov 2023 12:53:37 +0300
Subject: [PATCH] Add context for macro calls

Change-Id: I674b153770297c3a0abbfcee26d840e0f2be490b
---
 jenkins_jobs/modules/base.py                  | 20 +++++++++++++
 jenkins_jobs/modules/builders.py              |  5 ++--
 jenkins_jobs/modules/notifications.py         |  5 ++--
 jenkins_jobs/modules/parameters.py            | 11 +++++--
 jenkins_jobs/modules/properties.py            | 11 +++----
 jenkins_jobs/modules/publishers.py            |  6 ++--
 jenkins_jobs/modules/reporters.py             |  6 ++--
 jenkins_jobs/modules/scm.py                   | 14 +++++++--
 jenkins_jobs/modules/triggers.py              |  7 ++---
 jenkins_jobs/modules/wrappers.py              |  5 ++--
 jenkins_jobs/registry.py                      | 29 ++++++++++++++++---
 .../macro_error_context_for_builder.error     | 18 ++++++++++++
 .../macro_error_context_for_builder.yaml      | 17 +++++++++++
 ...macro_error_context_for_notification.error | 18 ++++++++++++
 .../macro_error_context_for_notification.yaml | 19 ++++++++++++
 .../macro_error_context_for_parameter.error   | 18 ++++++++++++
 .../macro_error_context_for_parameter.yaml    | 19 ++++++++++++
 ...macro_error_context_for_pipeline_scm.error | 18 ++++++++++++
 .../macro_error_context_for_pipeline_scm.yaml | 20 +++++++++++++
 .../macro_error_context_for_property.error    | 18 ++++++++++++
 .../macro_error_context_for_property.yaml     | 18 ++++++++++++
 .../macro_error_context_for_publisher.error   | 18 ++++++++++++
 .../macro_error_context_for_publisher.yaml    | 18 ++++++++++++
 .../macro_error_context_for_reporter.error    | 18 ++++++++++++
 .../macro_error_context_for_reporter.yaml     | 19 ++++++++++++
 .../macro_error_context_for_scm.error         | 18 ++++++++++++
 .../macro_error_context_for_scm.yaml          | 18 ++++++++++++
 .../macro_error_context_for_trigger.error     | 18 ++++++++++++
 .../macro_error_context_for_trigger.yaml      | 18 ++++++++++++
 .../macro_error_context_for_wrapper.error     | 18 ++++++++++++
 .../macro_error_context_for_wrapper.yaml      | 18 ++++++++++++
 .../missing_param_jinja2_macro_direct.error   |  3 ++
 .../missing_param_simple_macro_direct.error   |  3 ++
 33 files changed, 457 insertions(+), 32 deletions(-)
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_builder.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_builder.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_notification.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_notification.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_parameter.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_parameter.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_property.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_property.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_publisher.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_publisher.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_reporter.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_reporter.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_scm.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_scm.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_trigger.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_trigger.yaml
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.error
 create mode 100644 tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.yaml

diff --git a/jenkins_jobs/modules/base.py b/jenkins_jobs/modules/base.py
index df5d5c169..9cc4303fb 100644
--- a/jenkins_jobs/modules/base.py
+++ b/jenkins_jobs/modules/base.py
@@ -14,6 +14,8 @@
 
 # Base class for a jenkins_jobs module
 
+from jenkins_jobs.loc_loader import LocList
+
 
 class Base(object):
     """
@@ -73,3 +75,21 @@ class Base(object):
         """
 
         pass
+
+    def dispatch_component_list(
+        self, component_type, component_list, xml_parent, job_data=None
+    ):
+        if not component_list:
+            return
+        for idx, component in enumerate(component_list):
+            if isinstance(component_list, LocList):
+                pos = component_list.value_pos[idx]
+            else:
+                pos = None
+            self.registry.dispatch(
+                component_type,
+                xml_parent,
+                component,
+                job_data=job_data,
+                component_pos=pos,
+            )
diff --git a/jenkins_jobs/modules/builders.py b/jenkins_jobs/modules/builders.py
index e947daba2..91917723d 100644
--- a/jenkins_jobs/modules/builders.py
+++ b/jenkins_jobs/modules/builders.py
@@ -2576,9 +2576,8 @@ class Builders(jenkins_jobs.modules.base.Base):
 
         for alias in ["prebuilders", "builders", "postbuilders"]:
             if alias in data:
-                builders = XML.SubElement(xml_parent, alias)
-                for builder in data[alias]:
-                    self.registry.dispatch("builder", builders, builder)
+                xml_builders = XML.SubElement(xml_parent, alias)
+                self.dispatch_component_list("builder", data[alias], xml_builders)
 
         # Make sure freestyle projects always have a <builders> entry
         # or Jenkins v1.472 (at least) will NPE.
diff --git a/jenkins_jobs/modules/notifications.py b/jenkins_jobs/modules/notifications.py
index 196ed3bcd..5482484aa 100644
--- a/jenkins_jobs/modules/notifications.py
+++ b/jenkins_jobs/modules/notifications.py
@@ -95,5 +95,6 @@ class Notifications(jenkins_jobs.modules.base.Base):
             )
             endpoints_element = XML.SubElement(notify_element, "endpoints")
 
-            for endpoint in notifications:
-                self.registry.dispatch("notification", endpoints_element, endpoint)
+            self.dispatch_component_list(
+                "notification", notifications, endpoints_element
+            )
diff --git a/jenkins_jobs/modules/parameters.py b/jenkins_jobs/modules/parameters.py
index abfce4c0a..2b2b57b62 100644
--- a/jenkins_jobs/modules/parameters.py
+++ b/jenkins_jobs/modules/parameters.py
@@ -34,6 +34,7 @@ Example::
 
 import xml.etree.ElementTree as XML
 
+from jenkins_jobs.loc_loader import LocList
 from jenkins_jobs.errors import JenkinsJobsException
 from jenkins_jobs.errors import MissingAttributeError
 from jenkins_jobs.errors import InvalidAttributeError
@@ -1624,7 +1625,11 @@ class Parameters(jenkins_jobs.modules.base.Base):
             pdefs = pdefp.find("parameterDefinitions")
             if pdefs is None:
                 pdefs = XML.SubElement(pdefp, "parameterDefinitions")
-            for param in parameters:
+            for idx, param in enumerate(parameters):
+                if isinstance(parameters, LocList):
+                    param_pos = parameters.value_pos[idx]
+                else:
+                    param_pos = None
                 if not isinstance(param, dict):
                     # Macro parameter without arguments
                     param = {param: {}}
@@ -1640,4 +1645,6 @@ class Parameters(jenkins_jobs.modules.base.Base):
                             self._extend_uno_choice_param_data(
                                 macro_param, macro_param_type, data
                             )
-                self.registry.dispatch("parameter", pdefs, param)
+                self.registry.dispatch(
+                    "parameter", pdefs, param, component_pos=param_pos
+                )
diff --git a/jenkins_jobs/modules/properties.py b/jenkins_jobs/modules/properties.py
index 48aaa2a47..acf2d9748 100644
--- a/jenkins_jobs/modules/properties.py
+++ b/jenkins_jobs/modules/properties.py
@@ -1513,9 +1513,10 @@ class Properties(jenkins_jobs.modules.base.Base):
     component_list_type = "properties"
 
     def gen_xml(self, xml_parent, data):
-        properties = xml_parent.find("properties")
-        if properties is None:
-            properties = XML.SubElement(xml_parent, "properties")
+        xml_properties = xml_parent.find("properties")
+        if xml_properties is None:
+            xml_properties = XML.SubElement(xml_parent, "properties")
 
-        for prop in data.get("properties", []):
-            self.registry.dispatch("property", properties, prop, job_data=data)
+        self.dispatch_component_list(
+            "property", data.get("properties", []), xml_properties, job_data=data
+        )
diff --git a/jenkins_jobs/modules/publishers.py b/jenkins_jobs/modules/publishers.py
index e2f1219c8..c383984cf 100755
--- a/jenkins_jobs/modules/publishers.py
+++ b/jenkins_jobs/modules/publishers.py
@@ -8400,7 +8400,7 @@ class Publishers(jenkins_jobs.modules.base.Base):
             logger.debug("Publishers skipped for Pipeline job")
             return
 
-        publishers = XML.SubElement(xml_parent, "publishers")
+        xml_publishers = XML.SubElement(xml_parent, "publishers")
 
-        for action in data.get("publishers", []):
-            self.registry.dispatch("publisher", publishers, action)
+        component_list = data.get("publishers", [])
+        self.dispatch_component_list("publisher", component_list, xml_publishers)
diff --git a/jenkins_jobs/modules/reporters.py b/jenkins_jobs/modules/reporters.py
index e92cc25ca..98b85383a 100644
--- a/jenkins_jobs/modules/reporters.py
+++ b/jenkins_jobs/modules/reporters.py
@@ -151,7 +151,7 @@ class Reporters(jenkins_jobs.modules.base.Base):
                 "Reporters may only be used for Maven " "modules."
             )
 
-        reporters = XML.SubElement(xml_parent, "reporters")
+        xml_reporters = XML.SubElement(xml_parent, "reporters")
 
-        for action in data.get("reporters", []):
-            self.registry.dispatch("reporter", reporters, action)
+        component_list = data.get("reporters", [])
+        self.dispatch_component_list("reporter", component_list, xml_reporters)
diff --git a/jenkins_jobs/modules/scm.py b/jenkins_jobs/modules/scm.py
index 53716a481..b9da677b4 100644
--- a/jenkins_jobs/modules/scm.py
+++ b/jenkins_jobs/modules/scm.py
@@ -39,6 +39,7 @@ Example of an empty ``scm``:
 import logging
 import xml.etree.ElementTree as XML
 
+from jenkins_jobs.loc_loader import LocList
 from jenkins_jobs.errors import InvalidAttributeError
 from jenkins_jobs.errors import JenkinsJobsException
 import jenkins_jobs.modules.base
@@ -1769,8 +1770,8 @@ class SCM(jenkins_jobs.modules.base.Base):
             return
 
         scms_parent = XML.Element("scms")
-        for scm in data.get("scm", []):
-            self.registry.dispatch("scm", scms_parent, scm)
+        component_list = data.get("scm", [])
+        self.dispatch_component_list("scm", component_list, scms_parent)
         scms_count = len(scms_parent)
         if scms_count == 0:
             XML.SubElement(xml_parent, "scm", {"class": "hudson.scm.NullSCM"})
@@ -1806,7 +1807,14 @@ class PipelineSCM(jenkins_jobs.modules.base.Base):
             if scms_count == 0:
                 raise JenkinsJobsException("'scm' missing or empty")
             elif scms_count == 1:
-                self.registry.dispatch("scm", definition_parent, scms[0])
+                component = scms[0]
+                if isinstance(scms, LocList):
+                    component_pos = scms.value_pos[0]
+                else:
+                    component_pos = None
+                self.registry.dispatch(
+                    "scm", definition_parent, component, component_pos=component_pos
+                )
                 mapping = [
                     ("script-path", "scriptPath", "Jenkinsfile"),
                     ("lightweight-checkout", "lightweight", None, [True, False]),
diff --git a/jenkins_jobs/modules/triggers.py b/jenkins_jobs/modules/triggers.py
index c071b9f3d..0b23f7a87 100644
--- a/jenkins_jobs/modules/triggers.py
+++ b/jenkins_jobs/modules/triggers.py
@@ -2784,7 +2784,7 @@ class Triggers(jenkins_jobs.modules.base.Base):
             return
 
         if data.get("project-type", "freestyle") != "pipeline":
-            trig_e = XML.SubElement(xml_parent, "triggers", {"class": "vector"})
+            xml_triggers = XML.SubElement(xml_parent, "triggers", {"class": "vector"})
         else:
             properties = xml_parent.find("properties")
             if properties is None:
@@ -2793,7 +2793,6 @@ class Triggers(jenkins_jobs.modules.base.Base):
                 properties,
                 "org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty",
             )
-            trig_e = XML.SubElement(pipeline_trig_prop, "triggers")
+            xml_triggers = XML.SubElement(pipeline_trig_prop, "triggers")
 
-        for trigger in triggers:
-            self.registry.dispatch("trigger", trig_e, trigger)
+        self.dispatch_component_list("trigger", triggers, xml_triggers)
diff --git a/jenkins_jobs/modules/wrappers.py b/jenkins_jobs/modules/wrappers.py
index 361b32adf..b8f7eb8d6 100644
--- a/jenkins_jobs/modules/wrappers.py
+++ b/jenkins_jobs/modules/wrappers.py
@@ -3005,7 +3005,6 @@ class Wrappers(jenkins_jobs.modules.base.Base):
             logger.debug("Build wrappers skipped for Pipeline job")
             return
 
-        wrappers = XML.SubElement(xml_parent, "buildWrappers")
+        xml_wrappers = XML.SubElement(xml_parent, "buildWrappers")
 
-        for wrap in data.get("wrappers", []):
-            self.registry.dispatch("wrapper", wrappers, wrap)
+        self.dispatch_component_list("wrapper", data.get("wrappers", []), xml_wrappers)
diff --git a/jenkins_jobs/registry.py b/jenkins_jobs/registry.py
index 2c73744bb..391011f23 100644
--- a/jenkins_jobs/registry.py
+++ b/jenkins_jobs/registry.py
@@ -191,7 +191,13 @@ class ModuleRegistry(object):
         return component_list_type
 
     def dispatch(
-        self, component_type, xml_parent, component, template_data={}, job_data=None
+        self,
+        component_type,
+        xml_parent,
+        component,
+        template_data={},
+        job_data=None,
+        component_pos=None,
     ):
         """This is a method that you can call from your implementation of
         Base.gen_xml or component.  It allows modules to define a type
@@ -243,9 +249,24 @@ class ModuleRegistry(object):
         macro_dict = self.macros.get(component_type, {})
         macro = macro_dict.get(name)
         if macro:
-            self._dispatch_macro(
-                component_data, component_type, eps, job_data, macro, name, xml_parent
-            )
+            try:
+                self._dispatch_macro(
+                    component_data,
+                    component_type,
+                    eps,
+                    job_data,
+                    macro,
+                    name,
+                    xml_parent,
+                )
+            except JenkinsJobsException as x:
+                if component_pos is not None:
+                    raise x.with_context(
+                        f"While expanding {component_type} macro call {name!r}",
+                        pos=component_pos,
+                    )
+                else:
+                    raise
         elif name in eps:
             try:
                 func = eps[name]
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_builder.error b/tests/yamlparser/error_fixtures/macro_error_context_for_builder.error
new file mode 100644
index 000000000..7fcd3a06f
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_builder.error
@@ -0,0 +1,18 @@
+macro_error_context_for_builder.yaml:14:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_builder.yaml:17:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_builder.yaml:8:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_builder.yaml:11:7: While expanding builder macro call 'sample-builder'
+        - sample-builder:
+          ^
+macro_error_context_for_builder.yaml:3:3: While expanding macro 'sample-builder'
+    - builder:
+      ^
+macro_error_context_for_builder.yaml:6:15: While formatting string 'echo {missing_parameter}': Missing parameter: 'missing_parameter'
+        - shell: 'echo {missing_parameter}'
+                  ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_builder.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_builder.yaml
new file mode 100644
index 000000000..60e59accb
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_builder.yaml
@@ -0,0 +1,17 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- builder:
+    name: sample-builder
+    builders:
+    - shell: 'echo {missing_parameter}'
+
+- job-template:
+    name: sample-job
+    builders:
+    - sample-builder:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_notification.error b/tests/yamlparser/error_fixtures/macro_error_context_for_notification.error
new file mode 100644
index 000000000..fd1e38a43
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_notification.error
@@ -0,0 +1,18 @@
+macro_error_context_for_notification.yaml:16:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_notification.yaml:19:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_notification.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_notification.yaml:13:7: While expanding notification macro call 'sample-notification'
+        - sample-notification:
+          ^
+macro_error_context_for_notification.yaml:3:3: While expanding macro 'sample-notification'
+    - notification:
+      ^
+macro_error_context_for_notification.yaml:7:15: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            url: '{missing_parameter}'
+                  ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_notification.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_notification.yaml
new file mode 100644
index 000000000..8e2a69443
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_notification.yaml
@@ -0,0 +1,19 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- notification:
+    name: sample-notification
+    notifications:
+    - http:
+        url: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    project-type: maven
+    notifications:
+    - sample-notification:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.error b/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.error
new file mode 100644
index 000000000..2d7a6f515
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.error
@@ -0,0 +1,18 @@
+macro_error_context_for_parameter.yaml:16:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_parameter.yaml:19:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_parameter.yaml:10:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_parameter.yaml:13:7: While expanding parameter macro call 'sample-parameter'
+        - sample-parameter:
+          ^
+macro_error_context_for_parameter.yaml:3:3: While expanding macro 'sample-parameter'
+    - parameter:
+      ^
+macro_error_context_for_parameter.yaml:8:19: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            default: '{missing_parameter}'
+                      ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.yaml
new file mode 100644
index 000000000..80d264fd3
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_parameter.yaml
@@ -0,0 +1,19 @@
+# Check proper context is shown for error in parameter macro expansion.
+
+- parameter:
+    name: sample-parameter
+    parameters:
+    - string:
+        name: FOO
+        default: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    parameters:
+    - sample-parameter:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.error b/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.error
new file mode 100644
index 000000000..518ecfc63
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.error
@@ -0,0 +1,18 @@
+macro_error_context_for_pipeline_scm.yaml:17:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_pipeline_scm.yaml:20:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_pipeline_scm.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_pipeline_scm.yaml:14:9: While expanding scm macro call 'sample-scm'
+          - sample-scm:
+            ^
+macro_error_context_for_pipeline_scm.yaml:3:3: While expanding macro 'sample-scm'
+    - scm:
+      ^
+macro_error_context_for_pipeline_scm.yaml:7:15: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            url: '{missing_parameter}'
+                  ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.yaml
new file mode 100644
index 000000000..fe2c7c768
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_pipeline_scm.yaml
@@ -0,0 +1,20 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- scm:
+    name: sample-scm
+    scm:
+    - git:
+        url: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    project-type: pipeline
+    pipeline-scm:
+      scm:
+      - sample-scm:
+          unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_property.error b/tests/yamlparser/error_fixtures/macro_error_context_for_property.error
new file mode 100644
index 000000000..37d8f86ec
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_property.error
@@ -0,0 +1,18 @@
+macro_error_context_for_property.yaml:15:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_property.yaml:18:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_property.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_property.yaml:12:7: While expanding property macro call 'sample-property'
+        - sample-property:
+          ^
+macro_error_context_for_property.yaml:3:3: While expanding macro 'sample-property'
+    - property:
+      ^
+macro_error_context_for_property.yaml:7:28: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            number-of-builds: '{missing_parameter}'
+                               ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_property.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_property.yaml
new file mode 100644
index 000000000..c54bd95dd
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_property.yaml
@@ -0,0 +1,18 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- property:
+    name: sample-property
+    properties:
+    - branch-api:
+        number-of-builds: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    properties:
+    - sample-property:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.error b/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.error
new file mode 100644
index 000000000..28ebf036c
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.error
@@ -0,0 +1,18 @@
+macro_error_context_for_publisher.yaml:15:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_publisher.yaml:18:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_publisher.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_publisher.yaml:12:7: While expanding publisher macro call 'sample-publisher'
+        - sample-publisher:
+          ^
+macro_error_context_for_publisher.yaml:3:3: While expanding macro 'sample-publisher'
+    - publisher:
+      ^
+macro_error_context_for_publisher.yaml:7:21: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            artifacts: '{missing_parameter}'
+                        ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.yaml
new file mode 100644
index 000000000..9f0d41514
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_publisher.yaml
@@ -0,0 +1,18 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- publisher:
+    name: sample-publisher
+    publishers:
+    - archive:
+        artifacts: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    publishers:
+    - sample-publisher:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.error b/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.error
new file mode 100644
index 000000000..a0ca222d7
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.error
@@ -0,0 +1,18 @@
+macro_error_context_for_reporter.yaml:16:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_reporter.yaml:19:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_reporter.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_reporter.yaml:13:7: While expanding reporter macro call 'sample-reporter'
+        - sample-reporter:
+          ^
+macro_error_context_for_reporter.yaml:3:3: While expanding macro 'sample-reporter'
+    - reporter:
+      ^
+macro_error_context_for_reporter.yaml:7:21: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            recipient: '{missing_parameter}'
+                        ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.yaml
new file mode 100644
index 000000000..f0c460215
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_reporter.yaml
@@ -0,0 +1,19 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- reporter:
+    name: sample-reporter
+    reporters:
+    - email:
+        recipient: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    project-type: maven
+    reporters:
+    - sample-reporter:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_scm.error b/tests/yamlparser/error_fixtures/macro_error_context_for_scm.error
new file mode 100644
index 000000000..7085372d6
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_scm.error
@@ -0,0 +1,18 @@
+macro_error_context_for_scm.yaml:15:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_scm.yaml:18:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_scm.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_scm.yaml:12:7: While expanding scm macro call 'sample-scm'
+        - sample-scm:
+          ^
+macro_error_context_for_scm.yaml:3:3: While expanding macro 'sample-scm'
+    - scm:
+      ^
+macro_error_context_for_scm.yaml:7:15: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            url: '{missing_parameter}'
+                  ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_scm.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_scm.yaml
new file mode 100644
index 000000000..01dc39a95
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_scm.yaml
@@ -0,0 +1,18 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- scm:
+    name: sample-scm
+    scm:
+    - git:
+        url: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    scm:
+    - sample-scm:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.error b/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.error
new file mode 100644
index 000000000..87c736182
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.error
@@ -0,0 +1,18 @@
+macro_error_context_for_trigger.yaml:15:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_trigger.yaml:18:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_trigger.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_trigger.yaml:12:7: While expanding trigger macro call 'sample-trigger'
+        - sample-trigger:
+          ^
+macro_error_context_for_trigger.yaml:3:3: While expanding macro 'sample-trigger'
+    - trigger:
+      ^
+macro_error_context_for_trigger.yaml:7:16: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            cron: '{missing_parameter}'
+                   ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.yaml
new file mode 100644
index 000000000..c5e0eee8c
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_trigger.yaml
@@ -0,0 +1,18 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- trigger:
+    name: sample-trigger
+    triggers:
+    - pollscm:
+        cron: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    triggers:
+    - sample-trigger:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.error b/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.error
new file mode 100644
index 000000000..9cc16ed22
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.error
@@ -0,0 +1,18 @@
+macro_error_context_for_wrapper.yaml:15:3: In project 'sample-project'
+    - project:
+      ^
+macro_error_context_for_wrapper.yaml:18:9: Defined here
+          - sample-job
+            ^
+macro_error_context_for_wrapper.yaml:9:3: In job template 'sample-job'
+    - job-template:
+      ^
+macro_error_context_for_wrapper.yaml:12:7: While expanding wrapper macro call 'sample-wrapper'
+        - sample-wrapper:
+          ^
+macro_error_context_for_wrapper.yaml:3:3: While expanding macro 'sample-wrapper'
+    - wrapper:
+      ^
+macro_error_context_for_wrapper.yaml:7:19: While formatting string '{missing_parameter}': Missing parameter: 'missing_parameter'
+            timeout: '{missing_parameter}'
+                      ^
diff --git a/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.yaml b/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.yaml
new file mode 100644
index 000000000..d11352787
--- /dev/null
+++ b/tests/yamlparser/error_fixtures/macro_error_context_for_wrapper.yaml
@@ -0,0 +1,18 @@
+# Check proper context is shown for error in builder macro expansion.
+
+- wrapper:
+    name: sample-wrapper
+    wrappers:
+    - timeout:
+        timeout: '{missing_parameter}'
+
+- job-template:
+    name: sample-job
+    wrappers:
+    - sample-wrapper:
+        unused_param: abc
+
+- project:
+    name: sample-project
+    jobs:
+      - sample-job
diff --git a/tests/yamlparser/error_fixtures/missing_param_jinja2_macro_direct.error b/tests/yamlparser/error_fixtures/missing_param_jinja2_macro_direct.error
index cb16ff283..479f63c0f 100644
--- a/tests/yamlparser/error_fixtures/missing_param_jinja2_macro_direct.error
+++ b/tests/yamlparser/error_fixtures/missing_param_jinja2_macro_direct.error
@@ -7,6 +7,9 @@ missing_param_jinja2_macro_direct.yaml:4:9: Defined here
 missing_param_jinja2_macro_direct.yaml:12:3: In job template 'sample-job'
     - job-template:
       ^
+missing_param_jinja2_macro_direct.yaml:15:9: While expanding builder macro call 'sample-builder'
+          - sample-builder:
+            ^
 missing_param_jinja2_macro_direct.yaml:6:3: While expanding macro 'sample-builder'
     - builder:
       ^
diff --git a/tests/yamlparser/error_fixtures/missing_param_simple_macro_direct.error b/tests/yamlparser/error_fixtures/missing_param_simple_macro_direct.error
index 454ca3a13..33c79d046 100644
--- a/tests/yamlparser/error_fixtures/missing_param_simple_macro_direct.error
+++ b/tests/yamlparser/error_fixtures/missing_param_simple_macro_direct.error
@@ -7,6 +7,9 @@ missing_param_simple_macro_direct.yaml:4:9: Defined here
 missing_param_simple_macro_direct.yaml:12:3: In job template 'sample-job'
     - job-template:
       ^
+missing_param_simple_macro_direct.yaml:15:9: While expanding builder macro call 'sample-builder'
+          - sample-builder:
+            ^
 missing_param_simple_macro_direct.yaml:6:3: While expanding macro 'sample-builder'
     - builder:
       ^