diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index e2bea98d0f..c5e9443916 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -81,6 +81,7 @@ class Facet: def __init__(self, *parameters): self.status = None + self.passed_entities: set[ifcopenshell.entity_instance] = set() self.failures: list[FacetFailure] = [] for i, name in enumerate(self.parameters): setattr(self, name.replace("@", ""), parameters[i]) diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py index 5b4e0dc3cd..694af9d374 100644 --- a/src/ifctester/ifctester/ids.py +++ b/src/ifctester/ifctester/ids.py @@ -174,6 +174,7 @@ class Specification: self.instructions = instructions self.applicable_entities: list[ifcopenshell.entity_instance] = [] + self.passed_entities: set[ifcopenshell.entity_instance] = set() self.failed_entities: set[ifcopenshell.entity_instance] = set() self.status = None @@ -238,6 +239,7 @@ class Specification: def reset_status(self): self.applicable_entities.clear() + self.passed_entities: set[ifcopenshell.entity_instance] = set() self.failed_entities: set[ifcopenshell.entity_instance] = set() for facet in self.requirements: facet.status = None @@ -270,13 +272,19 @@ class Specification: result = facet(element) is_pass = bool(result) if self.maxOccurs != 0: # This is a required or optional specification - if not is_pass: + if is_pass: + self.passed_entities.add(element) + facet.passed_entities.add(element) + else: self.failed_entities.add(element) facet.failures.append(FacetFailure(element=element, reason=str(result))) else: # This is a prohibited specification if is_pass: self.failed_entities.add(element) facet.failures.append(FacetFailure(element=element, reason=str(result))) + else: + self.passed_entities.add(element) + facet.passed_entities.add(element) self.status = True for facet in self.requirements: diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index 66029d006b..db4b7b5e90 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -96,7 +96,8 @@ class ResultsRequirement(TypedDict): value: str description: str status: bool - failed_entities: list[ResultsFailedEntity] + passed_entities: list[ResultsEntity] + failed_entities: list[ResultsEntity] total_applicable: int total_pass: int total_fail: int @@ -104,8 +105,8 @@ class ResultsRequirement(TypedDict): # use different syntax because of the "class" key -ResultsFailedEntity = TypedDict( - "ResultsFailedEntity", +ResultsEntity = TypedDict( + "ResultsEntity", { "reason": str, "element": str, @@ -339,6 +340,7 @@ class Json(Reporter): value=value, description=requirement.to_string("requirement", specification, requirement), status=requirement.status, + passed_entities=self.report_passed_entities(requirement), failed_entities=self.report_failed_entities(requirement), total_applicable=total_applicable, total_pass=total_pass, @@ -370,9 +372,27 @@ class Json(Reporter): requirements=requirements, ) - def report_failed_entities(self, requirement: Facet) -> list[ResultsFailedEntity]: + def report_passed_entities(self, requirement: Facet) -> list[ResultsEntity]: return [ - ResultsFailedEntity( + ResultsEntity( + { + "element": e, + "element_type": ifcopenshell.util.element.get_type(e), + "class": e.is_a(), + "predefined_type": ifcopenshell.util.element.get_predefined_type(e), + "name": getattr(e, "Name", None), + "description": getattr(e, "Description", None), + "id": e.id(), + "global_id": getattr(e, "GlobalId", None), + "tag": getattr(e, "Tag", None), + } + ) + for e in requirement.passed_entities + ] + + def report_failed_entities(self, requirement: Facet) -> list[ResultsEntity]: + return [ + ResultsEntity( { "reason": f["reason"], "element": f["element"], @@ -413,11 +433,17 @@ class Html(Json): entity_limit = 100 for spec in self.results["specifications"]: for requirement in spec["requirements"]: - total = len(requirement["failed_entities"]) + total_passed_entities = len(requirement["passed_entities"]) + total_failed_entities = len(requirement["failed_entities"]) + requirement["passed_entities"] = requirement["passed_entities"][0:entity_limit] requirement["failed_entities"] = requirement["failed_entities"][0:entity_limit] - requirement["has_omitted"] = total > entity_limit - requirement["total_entities"] = total - requirement["total_omitted"] = total - entity_limit + requirement["total_failed_entities"] = total_failed_entities + requirement["total_omitted_failures"] = total_failed_entities - entity_limit + requirement["has_omitted_failures"] = total_failed_entities > entity_limit + requirement["total_passed_entities"] = total_passed_entities + requirement["total_omitted_passes"] = total_passed_entities - entity_limit + requirement["has_omitted_passes"] = total_passed_entities > entity_limit + def to_string(self) -> str: import pystache diff --git a/src/ifctester/ifctester/templates/report.html b/src/ifctester/ifctester/templates/report.html index 9cd9ce35b6..1ecaaec7dc 100644 --- a/src/ifctester/ifctester/templates/report.html +++ b/src/ifctester/ifctester/templates/report.html @@ -59,10 +59,13 @@ .percent { text-align: left; padding-top: 5px; padding-left: 5px; padding-bottom: 5px; color: white; border-radius: 5px; white-space: nowrap; } .pass { background-color: var(--green); } .fail { background-color: var(--red); } - table { width: 100%; border-bottom: 2px solid var(--red); border-spacing: 0; border-radius: 5px; margin-top: 10px; } - th, td { padding: 5px; } - thead>tr { background-color: var(--red); font-weight: bold; color: #fff; } - tbody tr { border-bottom: 1px solid var(--red); } + table { width: 100%; border-spacing: 0; border-radius: 5px; margin-top: 10px; } + th, td { padding: 5px; color: #000; } + table.pass { border-bottom: 2px solid var(--green); } + table.fail { border-bottom: 2px solid var(--red); } + table thead>tr { font-weight: bold; color: #000; } + table.pass tbody tr { border-bottom: 1px solid var(--green); } + table.fail tbody tr { border-bottom: 1px solid var(--red); } tbody tr:nth-child(odd) { background-color: rgba(1, 1, 1, 0.05); } tbody tr:nth-child(even) { background-color: rgba(1, 1, 1, 0.1); } tbody tr:hover { background-color: rgba(0, 0, 0, 0); } @@ -130,8 +133,39 @@ {{description}} + {{#total_pass}} + + + + + + + + + + + + + {{#passed_entities}} + + + + + + + + + {{/passed_entities}} + {{#has_omitted_passes}} + + + + {{/has_omitted_passes}} + +
ClassPredefinedTypeNameDescriptionGlobalIdTag
{{class}}{{predefined_type}}{{name}}{{description}}{{global_id}}{{tag}}
... {{total_omitted_passes}} more passing elements not shown out of {{total_passed_entities}} total ...
+ {{/total_pass}} {{#total_fail}} - +
@@ -155,11 +189,11 @@ {{/failed_entities}} - {{#has_omitted}} + {{#has_omitted_failures}} - + - {{/has_omitted}} + {{/has_omitted_failures}}
Class{{tag}}
... {{total_omitted}} more elements not shown out of {{total_entities}} total ... ... {{total_omitted_failures}} more failing elements not shown out of {{total_failed_entities}} total ...
{{/total_fail}}