mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 09:32:37 +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):
|
def before_all(context):
|
||||||
userdata = context.config.userdata
|
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
|
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
|
import gettext
|
||||||
from behave import step
|
import os
|
||||||
|
from behave import step, given
|
||||||
|
|
||||||
from ifcdata_methods import assert_schema
|
from ifcdata_methods import assert_schema
|
||||||
from utils import IfcFile
|
from utils import IfcFile
|
||||||
from utils import switch_locale
|
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')
|
@step('The IFC file "{file}" must be provided')
|
||||||
def step_impl(context, file):
|
def step_impl(context, file):
|
||||||
@@ -13,6 +22,19 @@ def step_impl(context, file):
|
|||||||
except:
|
except:
|
||||||
assert False, f"The file {file} could not be loaded"
|
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")
|
@step("IFC data must use the {schema} schema")
|
||||||
def step_impl(context, schema):
|
def step_impl(context, schema):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.express
|
||||||
import ifcopenshell.util
|
import ifcopenshell.util
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
@@ -10,6 +11,13 @@ class IfcFile(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path=None):
|
def load(cls, path=None):
|
||||||
cls.file = ifcopenshell.open(path)
|
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
|
@classmethod
|
||||||
def get(cls):
|
def get(cls):
|
||||||
@@ -23,6 +31,17 @@ class IfcFile(object):
|
|||||||
return cls.get().by_guid(guid)
|
return cls.get().by_guid(guid)
|
||||||
except:
|
except:
|
||||||
assert False, "An element with the ID {} could not be found.".format(guid)
|
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):
|
def assert_number(number):
|
||||||
|
|||||||
@@ -7,18 +7,20 @@
|
|||||||
<title>{{name}}</title>
|
<title>{{name}}</title>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
|
||||||
<style>
|
<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.time { color: #999; font-style: italic; float: right; }
|
||||||
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
|
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.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; }
|
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.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
|
||||||
p.unspecified { background-color: #994f00; 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;}
|
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 { padding: 10px; font-family: monospace; }
|
||||||
li.success { background-color: #b6cca1; color: #333; }
|
li.success { background-color: #b6cca1; color: #333; }
|
||||||
li.failure { background-color: #fbb4a8; color: #900; }
|
li.failure { background-color: #fbb4a8; color: #900; }
|
||||||
li.unspecified { background-color: #ffd37f; color: #a30; }
|
li.unspecified { background-color: #ffd37f; color: #a30; }
|
||||||
|
li.skipped { background-color: #f5f5f5; color: #333; }
|
||||||
li p { margin-bottom: 0px; }
|
li p { margin-bottom: 0px; }
|
||||||
footer { color: #999; font-size: 0.8em; }
|
footer { color: #999; font-size: 0.8em; }
|
||||||
header { text-align: center; }
|
header { text-align: center; }
|
||||||
@@ -52,11 +54,11 @@
|
|||||||
</p>
|
</p>
|
||||||
<ol>
|
<ol>
|
||||||
{{#steps}}
|
{{#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}}}
|
{{{name}}}
|
||||||
<span class="step-time">{{time}}s</span>
|
<span class="step-time">{{time}}s</span>
|
||||||
{{^is_success}}
|
{{^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}}
|
{{#error_message}}
|
||||||
{{.}}<br />
|
{{.}}<br />
|
||||||
{{/error_message}}
|
{{/error_message}}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import os
|
|||||||
import pystache
|
import pystache
|
||||||
|
|
||||||
|
|
||||||
def generate_report(adir="."):
|
def generate_report(adir=".", use_report_folder=True, report_file_name="", html_template_file_path=""):
|
||||||
print("# Generating HTML reports now.")
|
#print("# Generating HTML reports now.")
|
||||||
|
|
||||||
# get html template path
|
# get html template path
|
||||||
report_template_path = os.path.join(
|
report_template_path = os.path.join(
|
||||||
@@ -13,11 +13,20 @@ def generate_report(adir="."):
|
|||||||
"features/"
|
"features/"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if html_template_file_path:
|
||||||
|
report_template_path = html_template_file_path
|
||||||
|
|
||||||
# get report file
|
# 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):
|
if not os.path.exists(report_dir):
|
||||||
return print("No report directory was found.")
|
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)
|
# print(report_path)
|
||||||
if not os.path.exists(report_path):
|
if not os.path.exists(report_path):
|
||||||
return print("No report data was found.")
|
return print("No report data was found.")
|
||||||
@@ -58,7 +67,12 @@ def generate_report(adir="."):
|
|||||||
if "match" in step and "arguments" in step["match"]:
|
if "match" in step and "arguments" in step["match"]:
|
||||||
for a in step["match"]["arguments"]:
|
for a in step["match"]["arguments"]:
|
||||||
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
|
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"] = {}
|
||||||
step["result"]["status"] = "undefined"
|
step["result"]["status"] = "undefined"
|
||||||
step["result"]["duration"] = 0
|
step["result"]["duration"] = 0
|
||||||
@@ -68,7 +82,8 @@ def generate_report(adir="."):
|
|||||||
"name": name,
|
"name": name,
|
||||||
"time": round(step["result"]["duration"], 2),
|
"time": round(step["result"]["duration"], 2),
|
||||||
"is_success": step["result"]["status"] == "passed",
|
"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
|
"error_message": None
|
||||||
if step["result"]["status"] == "passed"
|
if step["result"]["status"] == "passed"
|
||||||
else step["result"]["error_message"],
|
else step["result"]["error_message"],
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ def run_tests(args):
|
|||||||
"--define",
|
"--define",
|
||||||
"localedir={}".format(locale_path)
|
"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)
|
behave_main(behave_args)
|
||||||
print("# All tests are finished.")
|
print("# All tests are finished.")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -42,6 +42,21 @@ if __name__ == "__main__":
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Generate a HTML report"
|
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(
|
parser.add_argument(
|
||||||
"-c",
|
"-c",
|
||||||
"--console",
|
"--console",
|
||||||
@@ -95,6 +110,12 @@ if __name__ == "__main__":
|
|||||||
args = vars(parser.parse_args())
|
args = vars(parser.parse_args())
|
||||||
print(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"]:
|
if args["purge"]:
|
||||||
clean.TestPurger().purge()
|
clean.TestPurger().purge()
|
||||||
elif args["report"]:
|
elif args["report"]:
|
||||||
@@ -103,4 +124,6 @@ if __name__ == "__main__":
|
|||||||
show_widget(args["featuresdir"], args["ifcfile"])
|
show_widget(args["featuresdir"], args["ifcfile"])
|
||||||
else:
|
else:
|
||||||
run.run_tests(args)
|
run.run_tests(args)
|
||||||
|
if args["report_after_run"]:
|
||||||
|
reports.generate_report()
|
||||||
print("# All tasks are complete :-)")
|
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