| 82 | |
| 83 | |
| 84 | class _NodeReporter: |
| 85 | def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: |
| 86 | self.id = nodeid |
| 87 | self.xml = xml |
| 88 | self.add_stats = self.xml.add_stats |
| 89 | self.family = self.xml.family |
| 90 | self.duration = 0.0 |
| 91 | self.properties: list[tuple[str, str]] = [] |
| 92 | self.nodes: list[ET.Element] = [] |
| 93 | self.attrs: dict[str, str] = {} |
| 94 | |
| 95 | def append(self, node: ET.Element) -> None: |
| 96 | self.xml.add_stats(node.tag) |
| 97 | self.nodes.append(node) |
| 98 | |
| 99 | def add_property(self, name: str, value: object) -> None: |
| 100 | self.properties.append((str(name), bin_xml_escape(value))) |
| 101 | |
| 102 | def add_attribute(self, name: str, value: object) -> None: |
| 103 | self.attrs[str(name)] = bin_xml_escape(value) |
| 104 | |
| 105 | def make_properties_node(self) -> ET.Element | None: |
| 106 | """Return a Junit node containing custom properties, if any.""" |
| 107 | if self.properties: |
| 108 | properties = ET.Element("properties") |
| 109 | for name, value in self.properties: |
| 110 | properties.append(ET.Element("property", name=name, value=value)) |
| 111 | return properties |
| 112 | return None |
| 113 | |
| 114 | def record_testreport(self, testreport: TestReport) -> None: |
| 115 | names = mangle_test_address(testreport.nodeid) |
| 116 | existing_attrs = self.attrs |
| 117 | classnames = names[:-1] |
| 118 | if self.xml.prefix: |
| 119 | classnames.insert(0, self.xml.prefix) |
| 120 | attrs: dict[str, str] = { |
| 121 | "classname": ".".join(classnames), |
| 122 | "name": bin_xml_escape(names[-1]), |
| 123 | "file": testreport.location[0], |
| 124 | } |
| 125 | if testreport.location[1] is not None: |
| 126 | attrs["line"] = str(testreport.location[1]) |
| 127 | if hasattr(testreport, "url"): |
| 128 | attrs["url"] = testreport.url |
| 129 | self.attrs = attrs |
| 130 | self.attrs.update(existing_attrs) # Restore any user-defined attributes. |
| 131 | |
| 132 | # Preserve legacy testcase behavior. |
| 133 | if self.family == "xunit1": |
| 134 | return |
| 135 | |
| 136 | # Filter out attributes not permitted by this test family. |
| 137 | # Including custom attributes because they are not valid here. |
| 138 | temp_attrs = {} |
| 139 | for key in self.attrs: |
| 140 | if key in families[self.family]["testcase"]: |
| 141 | temp_attrs[key] = self.attrs[key] |