mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Merge pull request #1184 from IFCRail/v0.6.0-ifcrail-mod
Set of improvements for BIMTester
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Dependency and build folders created by the build scripts
|
||||
/build/
|
||||
/dist/
|
||||
@@ -3,5 +3,6 @@ from behave.model import Scenario
|
||||
|
||||
def before_all(context):
|
||||
userdata = context.config.userdata
|
||||
continue_after_failed = True
|
||||
context.localedir = userdata.get("localedir")
|
||||
continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True)
|
||||
Scenario.continue_after_failed_step = continue_after_failed
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
from behave import step, given, when, then, use_step_matcher
|
||||
|
||||
from utils import IfcFile
|
||||
|
||||
use_step_matcher("parse")
|
||||
@step(u"There must be exactly {number} {ifc_class} element")
|
||||
@step(u"There must be exactly {number} {ifc_class} elements")
|
||||
def step_impl(context, number, ifc_class):
|
||||
num = len(IfcFile.get().by_type(ifc_class))
|
||||
assert num == int(number), "Could not find {} elements of {}. Found {} element(s).".format(number, ifc_class, num)
|
||||
|
||||
@given(u'a set of specific related elements')
|
||||
def step_impl(context):
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel()
|
||||
for row in context.table:
|
||||
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
|
||||
|
||||
@given(u'a set of specific related elements taken from the file "{path_file}"')
|
||||
def step_impl(context, path_file):
|
||||
import csv
|
||||
import os
|
||||
model = getattr(context, "model", None)
|
||||
if not model:
|
||||
context.model = TableModel()
|
||||
if context.config.userdata.get('path'):
|
||||
path_file = os.path.join(context.config.userdata.get('path'), path_file)
|
||||
if not os.path.exists(path_file):
|
||||
assert False, "File {} not found".format(path_file)
|
||||
with open(path_file, 'r', encoding="utf-8-sig") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
|
||||
|
||||
@then(u'there must be exactly a number of {ifc_class} equals to the number of distinct row value')
|
||||
def step_impl(context, ifc_class):
|
||||
try:
|
||||
context.execute_steps(u"""
|
||||
then There must be exactly {number} {ifc_class} elements
|
||||
""".format(ifc_class=ifc_class, number=context.model.get_count_distinct_values()))
|
||||
except AssertionError as error:
|
||||
str_error = str(error)
|
||||
assert False, str_error[:str_error.find("Traceback")]
|
||||
assert True
|
||||
|
||||
@then(u'there is a relationship {ifc_class} with {left_attribute} and {right_attribute} between the two elements of each row')
|
||||
def step_impl(context, ifc_class, left_attribute, right_attribute):
|
||||
rows = context.model.rows
|
||||
elements = IfcFile.by_type(ifc_class)
|
||||
errors = []
|
||||
for key, value in rows.items():
|
||||
found = False
|
||||
for element in elements:
|
||||
if any(x.Name == key for x in getattr(element, left_attribute))\
|
||||
and getattr(element, right_attribute).Name == value:
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f'The row ({key}, {value}) does not have the relationship.')
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
use_step_matcher("re")
|
||||
@step("all IfcGroup must be linked to a type in the list (?P<linked_ifc_classes>.*)")
|
||||
def step_impl(context, linked_ifc_classes):
|
||||
groups = IfcFile.by_type("IfcGroup")
|
||||
errors = []
|
||||
for group in groups:
|
||||
if not hasattr(group, "IsGroupedBy"):
|
||||
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
|
||||
else:
|
||||
for grouped_by in getattr(group, "IsGroupedBy"):
|
||||
if not hasattr(grouped_by, "RelatedObjects"):
|
||||
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
|
||||
else:
|
||||
for related_object in getattr(grouped_by, "RelatedObjects"):
|
||||
found = False
|
||||
for linked_ifc_class in linked_ifc_classes.split(","):
|
||||
if(related_object.is_a(linked_ifc_class)):
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f'The element "{related_object.Name}" does not have the right associated type.')
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row key')
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
|
||||
|
||||
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row value')
|
||||
def step_impl(context, ifc_types, attribute_name):
|
||||
values = set(context.model.rows.values())
|
||||
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
|
||||
|
||||
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
|
||||
errors = []
|
||||
# retrieve all elements of that type
|
||||
elements = IfcFile.by_types(ifc_types)
|
||||
# loop
|
||||
for attribute_value in attribute_values:
|
||||
found = False
|
||||
for element in elements:
|
||||
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
|
||||
found = True
|
||||
if not found:
|
||||
errors.append(f'An element with {attribute_name} attribute "{attribute_value}" was not found.')
|
||||
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
|
||||
|
||||
class TableModel(object):
|
||||
"""This class represents a table of data."""
|
||||
def __init__(self):
|
||||
self.rows = dict()
|
||||
|
||||
def add_row(self, related, relating):
|
||||
self.rows[related] = relating
|
||||
|
||||
def get_count(self):
|
||||
return len(self.rows)
|
||||
|
||||
def get_count_distinct_values(self):
|
||||
return len(set(self.rows.values()))
|
||||
@@ -1,10 +1,19 @@
|
||||
import gettext
|
||||
from behave import step
|
||||
import os
|
||||
from behave import step, given
|
||||
|
||||
from ifcdata_methods import assert_schema
|
||||
from utils import IfcFile
|
||||
from utils import switch_locale
|
||||
|
||||
@step('The IFC schema "{schema}" must be provided')
|
||||
def step_impl(context, schema):
|
||||
try:
|
||||
if context.config.userdata.get('path'):
|
||||
schema = os.path.join(context.config.userdata.get('path'), schema)
|
||||
IfcFile.load_schema(schema)
|
||||
except:
|
||||
assert False, f"The schema {schema} could not be loaded"
|
||||
|
||||
@step('The IFC file "{file}" must be provided')
|
||||
def step_impl(context, file):
|
||||
@@ -13,6 +22,19 @@ def step_impl(context, file):
|
||||
except:
|
||||
assert False, f"The file {file} could not be loaded"
|
||||
|
||||
@given('The IFC file has been provided through an argument')
|
||||
def step_impl(context):
|
||||
try:
|
||||
IfcFile.load(context.config.userdata.get("ifcfile"))
|
||||
except:
|
||||
assert False, f"The IFC {context.config.userdata.get('ifcfile')} file could not be loaded"
|
||||
|
||||
@given('A file path has been provided through an argument')
|
||||
def step_impl(context):
|
||||
try:
|
||||
assert context.config.userdata.get("path")
|
||||
except:
|
||||
assert False, f"The path {context.config.userdata.get('path')} could not be loaded"
|
||||
|
||||
@step("IFC data must use the {schema} schema")
|
||||
def step_impl(context, schema):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.express
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.element
|
||||
|
||||
@@ -10,6 +11,13 @@ class IfcFile(object):
|
||||
@classmethod
|
||||
def load(cls, path=None):
|
||||
cls.file = ifcopenshell.open(path)
|
||||
if not cls.file:
|
||||
assert False
|
||||
|
||||
@classmethod
|
||||
def load_schema(cls, path=None):
|
||||
schema = ifcopenshell.express.parse(path)
|
||||
ifcopenshell.register_schema(schema)
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
@@ -23,6 +31,17 @@ class IfcFile(object):
|
||||
return cls.get().by_guid(guid)
|
||||
except:
|
||||
assert False, "An element with the ID {} could not be found.".format(guid)
|
||||
|
||||
@classmethod
|
||||
def by_type(cls, ifc_type):
|
||||
return cls.get().by_type(ifc_type.strip())
|
||||
|
||||
@classmethod
|
||||
def by_types(cls, ifc_types):
|
||||
elements = []
|
||||
for ifc_type in ifc_types.split(","):
|
||||
elements += cls.by_type(ifc_type.strip())
|
||||
return elements
|
||||
|
||||
|
||||
def assert_number(number):
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
<title>{{name}}</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { font-family: 'Arial', sans-serif; padding: 40px; }
|
||||
body { font-family: 'Arial', sans-serif; padding: 10px 40px; }
|
||||
span.time { color: #999; font-style: italic; float: right; }
|
||||
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
|
||||
span.success { background-color: #97cc64; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
|
||||
span.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
|
||||
p.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.unspecified { background-color: #994f00; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.skipped { background-color: #8b8d8f; padding: 5px; border-radius: 5px; color: #fff; }
|
||||
p.description { background-color: #eee; border-radius: 5px; padding: 20px; margin-left: auto; margin-right: auto; display: inline-block; font-weight: bold;}
|
||||
li { padding: 10px; font-family: monospace; }
|
||||
li.success { background-color: #b6cca1; color: #333; }
|
||||
li.failure { background-color: #fbb4a8; color: #900; }
|
||||
li.unspecified { background-color: #ffd37f; color: #a30; }
|
||||
li.skipped { background-color: #f5f5f5; color: #333; }
|
||||
li p { margin-bottom: 0px; }
|
||||
footer { color: #999; font-size: 0.8em; }
|
||||
header { text-align: center; }
|
||||
@@ -52,11 +54,11 @@
|
||||
</p>
|
||||
<ol>
|
||||
{{#steps}}
|
||||
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}">
|
||||
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
|
||||
{{{name}}}
|
||||
<span class="step-time">{{time}}s</span>
|
||||
{{^is_success}}
|
||||
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}">
|
||||
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
|
||||
{{#error_message}}
|
||||
{{.}}<br />
|
||||
{{/error_message}}
|
||||
|
||||
@@ -4,8 +4,8 @@ import os
|
||||
import pystache
|
||||
|
||||
|
||||
def generate_report(adir="."):
|
||||
print("# Generating HTML reports now.")
|
||||
def generate_report(adir=".", use_report_folder=True, report_file_name="", html_template_file_path=""):
|
||||
#print("# Generating HTML reports now.")
|
||||
|
||||
# get html template path
|
||||
report_template_path = os.path.join(
|
||||
@@ -13,11 +13,20 @@ def generate_report(adir="."):
|
||||
"features/"
|
||||
)
|
||||
|
||||
if html_template_file_path:
|
||||
report_template_path = html_template_file_path
|
||||
|
||||
# get report file
|
||||
report_dir = os.path.join(adir, "report")
|
||||
report_dir = adir
|
||||
if use_report_folder:
|
||||
report_dir = os.path.join(adir, "report")
|
||||
if not os.path.exists(report_dir):
|
||||
return print("No report directory was found.")
|
||||
report_path = os.path.join(report_dir, "report.json")
|
||||
|
||||
if report_file_name:
|
||||
report_path = os.path.join(report_dir, report_file_name)
|
||||
else:
|
||||
report_path = os.path.join(report_dir, "report.json")
|
||||
# print(report_path)
|
||||
if not os.path.exists(report_path):
|
||||
return print("No report data was found.")
|
||||
@@ -58,7 +67,12 @@ def generate_report(adir="."):
|
||||
if "match" in step and "arguments" in step["match"]:
|
||||
for a in step["match"]["arguments"]:
|
||||
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
|
||||
if "result" not in step or step["result"]["status"] == "undefined":
|
||||
if "result" not in step:
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "skipped"
|
||||
step["result"]["duration"] = 0
|
||||
step["result"]["error_message"] = "This requirement has been skipped due to a previous failing step."
|
||||
elif step["result"]["status"] == "undefined":
|
||||
step["result"] = {}
|
||||
step["result"]["status"] = "undefined"
|
||||
step["result"]["duration"] = 0
|
||||
@@ -68,7 +82,8 @@ def generate_report(adir="."):
|
||||
"name": name,
|
||||
"time": round(step["result"]["duration"], 2),
|
||||
"is_success": step["result"]["status"] == "passed",
|
||||
"is_unspecified": "result" not in step or step["result"]["status"] == "undefined",
|
||||
"is_unspecified": step["result"]["status"] == "undefined",
|
||||
"is_skipped": step["result"]["status"] == "skipped",
|
||||
"error_message": None
|
||||
if step["result"]["status"] == "passed"
|
||||
else step["result"]["error_message"],
|
||||
|
||||
@@ -43,6 +43,10 @@ def run_tests(args):
|
||||
"--define",
|
||||
"localedir={}".format(locale_path)
|
||||
])
|
||||
if args["ifcfile"]:
|
||||
behave_args.extend(["--define", "ifcfile={}".format(args["ifcfile"])])
|
||||
if args["path"]:
|
||||
behave_args.extend(["--define", "path={}".format(args["path"])])
|
||||
behave_main(behave_args)
|
||||
print("# All tests are finished.")
|
||||
return True
|
||||
|
||||
@@ -42,6 +42,21 @@ if __name__ == "__main__":
|
||||
action="store_true",
|
||||
help="Generate a HTML report"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-rr",
|
||||
"--report_after_run",
|
||||
action="store_true",
|
||||
help="Generate a HTML report after running the tests"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-path",
|
||||
"--path",
|
||||
type=str,
|
||||
help=(
|
||||
"Specify a path to prepend to feature and ifc file"
|
||||
),
|
||||
default=""
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--console",
|
||||
@@ -95,6 +110,12 @@ if __name__ == "__main__":
|
||||
args = vars(parser.parse_args())
|
||||
print(args)
|
||||
|
||||
if args["path"]:
|
||||
if args["feature"]:
|
||||
args["feature"] = os.path.join(args["path"], args["feature"])
|
||||
if not args["gui"]:
|
||||
args["ifcfile"] = os.path.join(args["path"], args["ifcfile"])
|
||||
|
||||
if args["purge"]:
|
||||
clean.TestPurger().purge()
|
||||
elif args["report"]:
|
||||
@@ -103,4 +124,6 @@ if __name__ == "__main__":
|
||||
show_widget(args["featuresdir"], args["ifcfile"])
|
||||
else:
|
||||
run.run_tests(args)
|
||||
if args["report_after_run"]:
|
||||
reports.generate_report()
|
||||
print("# All tasks are complete :-)")
|
||||
|
||||
Submodule src/ifcopenshell-python/ifcopenshell/mvd deleted from 2f0ce53d56
-1
Submodule test/input deleted from 68784d9511
Reference in New Issue
Block a user