Skip to content
This repository was archived by the owner on Nov 24, 2024. It is now read-only.

Commit d7034c6

Browse files
authored
Merge pull request #2 from IfcOpenShell/v0.6.0
merge
2 parents ac632d0 + 1a48343 commit d7034c6

File tree

17 files changed

+1066
-135
lines changed

17 files changed

+1066
-135
lines changed

nix/patches/pr622.patch

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
From a0deb4ce8b43cf3c8b8c0a4225c6be5296446dbd Mon Sep 17 00:00:00 2001
2+
From: Adam Eri <adam.eri@blackmirror.media>
3+
Date: Tue, 3 Sep 2019 23:30:20 +0200
4+
Subject: [PATCH] Resolves compile error on macOS
5+
6+
Resolves "no member named 'isnan' in namespace 'std'" on macOS
7+
---
8+
GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp | 1 +
9+
1 file changed, 1 insertion(+)
10+
11+
diff --git a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
12+
index 1f9a3eef..dd6f5c59 100644
13+
--- a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
14+
+++ b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
15+
@@ -10,6 +10,7 @@
16+
17+
#include "GeneratedSaxParserUtils.h"
18+
#include <math.h>
19+
+#include <cmath>
20+
#include <memory>
21+
#include <string.h>
22+
#include <limits>

src/ifcbimtester/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Dependency and build folders created by the build scripts
2+
/build/
3+
/dist/

src/ifcbimtester/bimtester/features/environment.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33

44
def before_all(context):
55
userdata = context.config.userdata
6-
continue_after_failed = True
6+
context.localedir = userdata.get("localedir")
7+
continue_after_failed = userdata.getbool("runner.continue_after_failed_step", True)
78
Scenario.continue_after_failed_step = continue_after_failed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from behave import step, given, when, then, use_step_matcher
2+
3+
from utils import IfcFile
4+
5+
use_step_matcher("parse")
6+
@step(u"There must be exactly {number} {ifc_class} element")
7+
@step(u"There must be exactly {number} {ifc_class} elements")
8+
def step_impl(context, number, ifc_class):
9+
num = len(IfcFile.get().by_type(ifc_class))
10+
assert num == int(number), "Could not find {} elements of {}. Found {} element(s).".format(number, ifc_class, num)
11+
12+
@given(u'a set of specific related elements')
13+
def step_impl(context):
14+
model = getattr(context, "model", None)
15+
if not model:
16+
context.model = TableModel()
17+
for row in context.table:
18+
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
19+
20+
@given(u'a set of specific related elements taken from the file "{path_file}"')
21+
def step_impl(context, path_file):
22+
import csv
23+
import os
24+
model = getattr(context, "model", None)
25+
if not model:
26+
context.model = TableModel()
27+
if context.config.userdata.get('path'):
28+
path_file = os.path.join(context.config.userdata.get('path'), path_file)
29+
if not os.path.exists(path_file):
30+
assert False, "File {} not found".format(path_file)
31+
with open(path_file, 'r', encoding="utf-8-sig") as csvfile:
32+
reader = csv.DictReader(csvfile)
33+
for row in reader:
34+
context.model.add_row(row["RelatedObjects"], row["RelatingGroup"])
35+
36+
@then(u'there must be exactly a number of {ifc_class} equals to the number of distinct row value')
37+
def step_impl(context, ifc_class):
38+
try:
39+
context.execute_steps(u"""
40+
then There must be exactly {number} {ifc_class} elements
41+
""".format(ifc_class=ifc_class, number=context.model.get_count_distinct_values()))
42+
except AssertionError as error:
43+
str_error = str(error)
44+
assert False, str_error[:str_error.find("Traceback")]
45+
assert True
46+
47+
@then(u'there is a relationship {ifc_class} with {left_attribute} and {right_attribute} between the two elements of each row')
48+
def step_impl(context, ifc_class, left_attribute, right_attribute):
49+
rows = context.model.rows
50+
elements = IfcFile.by_type(ifc_class)
51+
errors = []
52+
for key, value in rows.items():
53+
found = False
54+
for element in elements:
55+
if any(x.Name == key for x in getattr(element, left_attribute))\
56+
and getattr(element, right_attribute).Name == value:
57+
found = True
58+
if not found:
59+
errors.append(f'The row ({key}, {value}) does not have the relationship.')
60+
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
61+
62+
use_step_matcher("re")
63+
@step("all IfcGroup must be linked to a type in the list (?P<linked_ifc_classes>.*)")
64+
def step_impl(context, linked_ifc_classes):
65+
groups = IfcFile.by_type("IfcGroup")
66+
errors = []
67+
for group in groups:
68+
if not hasattr(group, "IsGroupedBy"):
69+
errors.append(f'The element "{group.Name}" has no "IsGroupedBy" attribute.')
70+
else:
71+
for grouped_by in getattr(group, "IsGroupedBy"):
72+
if not hasattr(grouped_by, "RelatedObjects"):
73+
errors.append(f'The element "{grouped_by.Name}" has no "RelatedObjects" attribute.')
74+
else:
75+
for related_object in getattr(grouped_by, "RelatedObjects"):
76+
found = False
77+
for linked_ifc_class in linked_ifc_classes.split(","):
78+
if(related_object.is_a(linked_ifc_class)):
79+
found = True
80+
if not found:
81+
errors.append(f'The element "{related_object.Name}" does not have the right associated type.')
82+
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
83+
84+
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row key')
85+
def step_impl(context, ifc_types, attribute_name):
86+
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, context.model.rows.keys())
87+
88+
@then(u'there is an element of type (?P<ifc_types>.*) with a (?P<attribute_name>.*) attribute for each row value')
89+
def step_impl(context, ifc_types, attribute_name):
90+
values = set(context.model.rows.values())
91+
check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, values)
92+
93+
def check_if_element_exists_by_types_with_attribute_name(ifc_types, attribute_name, attribute_values):
94+
errors = []
95+
# retrieve all elements of that type
96+
elements = IfcFile.by_types(ifc_types)
97+
# loop
98+
for attribute_value in attribute_values:
99+
found = False
100+
for element in elements:
101+
if hasattr(element, attribute_name) and getattr(element, attribute_name) == attribute_value:
102+
found = True
103+
if not found:
104+
errors.append(f'An element with {attribute_name} attribute "{attribute_value}" was not found.')
105+
assert not errors, "Errors occured:\n{}".format("\n".join(errors))
106+
107+
class TableModel(object):
108+
"""This class represents a table of data."""
109+
def __init__(self):
110+
self.rows = dict()
111+
112+
def add_row(self, related, relating):
113+
self.rows[related] = relating
114+
115+
def get_count(self):
116+
return len(self.rows)
117+
118+
def get_count_distinct_values(self):
119+
return len(set(self.rows.values()))

src/ifcbimtester/bimtester/features/steps/ifcdata.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import gettext
2-
from behave import step
2+
import os
3+
from behave import step, given
34

45
from ifcdata_methods import assert_schema
56
from utils import IfcFile
67
from utils import switch_locale
78

9+
@step('The IFC schema "{schema}" must be provided')
10+
def step_impl(context, schema):
11+
try:
12+
if context.config.userdata.get('path'):
13+
schema = os.path.join(context.config.userdata.get('path'), schema)
14+
IfcFile.load_schema(schema)
15+
except:
16+
assert False, f"The schema {schema} could not be loaded"
817

918
@step('The IFC file "{file}" must be provided')
1019
def step_impl(context, file):
@@ -13,6 +22,19 @@ def step_impl(context, file):
1322
except:
1423
assert False, f"The file {file} could not be loaded"
1524

25+
@given('The IFC file has been provided through an argument')
26+
def step_impl(context):
27+
try:
28+
IfcFile.load(context.config.userdata.get("ifcfile"))
29+
except:
30+
assert False, f"The IFC {context.config.userdata.get('ifcfile')} file could not be loaded"
31+
32+
@given('A file path has been provided through an argument')
33+
def step_impl(context):
34+
try:
35+
assert context.config.userdata.get("path")
36+
except:
37+
assert False, f"The path {context.config.userdata.get('path')} could not be loaded"
1638

1739
@step("IFC data must use the {schema} schema")
1840
def step_impl(context, schema):

src/ifcbimtester/bimtester/features/steps/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ifcopenshell
2+
import ifcopenshell.express
23
import ifcopenshell.util
34
import ifcopenshell.util.element
45

@@ -10,6 +11,13 @@ class IfcFile(object):
1011
@classmethod
1112
def load(cls, path=None):
1213
cls.file = ifcopenshell.open(path)
14+
if not cls.file:
15+
assert False
16+
17+
@classmethod
18+
def load_schema(cls, path=None):
19+
schema = ifcopenshell.express.parse(path)
20+
ifcopenshell.register_schema(schema)
1321

1422
@classmethod
1523
def get(cls):
@@ -23,6 +31,17 @@ def by_guid(cls, guid):
2331
return cls.get().by_guid(guid)
2432
except:
2533
assert False, "An element with the ID {} could not be found.".format(guid)
34+
35+
@classmethod
36+
def by_type(cls, ifc_type):
37+
return cls.get().by_type(ifc_type.strip())
38+
39+
@classmethod
40+
def by_types(cls, ifc_types):
41+
elements = []
42+
for ifc_type in ifc_types.split(","):
43+
elements += cls.by_type(ifc_type.strip())
44+
return elements
2645

2746

2847
def assert_number(number):

src/ifcbimtester/bimtester/features/template.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@
77
<title>{{name}}</title>
88
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
99
<style>
10-
body { font-family: 'Arial', sans-serif; padding: 40px; }
10+
body { font-family: 'Arial', sans-serif; padding: 10px 40px; }
1111
span.time { color: #999; font-style: italic; float: right; }
1212
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
1313
span.success { background-color: #97cc64; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
1414
span.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
1515
p.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
1616
p.unspecified { background-color: #994f00; padding: 5px; border-radius: 5px; color: #fff; }
17+
p.skipped { background-color: #8b8d8f; padding: 5px; border-radius: 5px; color: #fff; }
1718
p.description { background-color: #eee; border-radius: 5px; padding: 20px; margin-left: auto; margin-right: auto; display: inline-block; font-weight: bold;}
1819
li { padding: 10px; font-family: monospace; }
1920
li.success { background-color: #b6cca1; color: #333; }
2021
li.failure { background-color: #fbb4a8; color: #900; }
2122
li.unspecified { background-color: #ffd37f; color: #a30; }
23+
li.skipped { background-color: #f5f5f5; color: #333; }
2224
li p { margin-bottom: 0px; }
2325
footer { color: #999; font-size: 0.8em; }
2426
header { text-align: center; }
@@ -52,11 +54,11 @@ <h2>{{name}}</h2>
5254
</p>
5355
<ol>
5456
{{#steps}}
55-
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}">
57+
<li class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
5658
{{{name}}}
5759
<span class="step-time">{{time}}s</span>
5860
{{^is_success}}
59-
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}">
61+
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
6062
{{#error_message}}
6163
{{.}}<br />
6264
{{/error_message}}

src/ifcbimtester/bimtester/reports.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,29 @@
44
import pystache
55

66

7-
def generate_report(adir="."):
8-
print("# Generating HTML reports now.")
7+
def generate_report(adir=".", use_report_folder=True, report_file_name="", html_template_file_path=""):
8+
#print("# Generating HTML reports now.")
99

1010
# get html template path
1111
report_template_path = os.path.join(
1212
os.path.dirname(os.path.realpath(__file__)),
1313
"features/"
1414
)
1515

16+
if html_template_file_path:
17+
report_template_path = html_template_file_path
18+
1619
# get report file
17-
report_dir = os.path.join(adir, "report")
20+
report_dir = adir
21+
if use_report_folder:
22+
report_dir = os.path.join(adir, "report")
1823
if not os.path.exists(report_dir):
1924
return print("No report directory was found.")
20-
report_path = os.path.join(report_dir, "report.json")
25+
26+
if report_file_name:
27+
report_path = os.path.join(report_dir, report_file_name)
28+
else:
29+
report_path = os.path.join(report_dir, "report.json")
2130
# print(report_path)
2231
if not os.path.exists(report_path):
2332
return print("No report data was found.")
@@ -58,7 +67,12 @@ def generate_report(adir="."):
5867
if "match" in step and "arguments" in step["match"]:
5968
for a in step["match"]["arguments"]:
6069
name = name.replace(a["value"], "<b>" + a["value"] + "</b>")
61-
if "result" not in step or step["result"]["status"] == "undefined":
70+
if "result" not in step:
71+
step["result"] = {}
72+
step["result"]["status"] = "skipped"
73+
step["result"]["duration"] = 0
74+
step["result"]["error_message"] = "This requirement has been skipped due to a previous failing step."
75+
elif step["result"]["status"] == "undefined":
6276
step["result"] = {}
6377
step["result"]["status"] = "undefined"
6478
step["result"]["duration"] = 0
@@ -68,7 +82,8 @@ def generate_report(adir="."):
6882
"name": name,
6983
"time": round(step["result"]["duration"], 2),
7084
"is_success": step["result"]["status"] == "passed",
71-
"is_unspecified": "result" not in step or step["result"]["status"] == "undefined",
85+
"is_unspecified": step["result"]["status"] == "undefined",
86+
"is_skipped": step["result"]["status"] == "skipped",
7287
"error_message": None
7388
if step["result"]["status"] == "passed"
7489
else step["result"]["error_message"],

src/ifcbimtester/bimtester/run.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ def run_tests(args):
4343
"--define",
4444
"localedir={}".format(locale_path)
4545
])
46+
if args["ifcfile"]:
47+
behave_args.extend(["--define", "ifcfile={}".format(args["ifcfile"])])
48+
if args["path"]:
49+
behave_args.extend(["--define", "path={}".format(args["path"])])
4650
behave_main(behave_args)
4751
print("# All tests are finished.")
4852
return True

src/ifcbimtester/startbimtester.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ def show_widget(features="", ifcfile=""):
4242
action="store_true",
4343
help="Generate a HTML report"
4444
)
45+
parser.add_argument(
46+
"-rr",
47+
"--report_after_run",
48+
action="store_true",
49+
help="Generate a HTML report after running the tests"
50+
)
51+
parser.add_argument(
52+
"-path",
53+
"--path",
54+
type=str,
55+
help=(
56+
"Specify a path to prepend to feature and ifc file"
57+
),
58+
default=""
59+
)
4560
parser.add_argument(
4661
"-c",
4762
"--console",
@@ -95,6 +110,12 @@ def show_widget(features="", ifcfile=""):
95110
args = vars(parser.parse_args())
96111
print(args)
97112

113+
if args["path"]:
114+
if args["feature"]:
115+
args["feature"] = os.path.join(args["path"], args["feature"])
116+
if not args["gui"]:
117+
args["ifcfile"] = os.path.join(args["path"], args["ifcfile"])
118+
98119
if args["purge"]:
99120
clean.TestPurger().purge()
100121
elif args["report"]:
@@ -103,4 +124,6 @@ def show_widget(features="", ifcfile=""):
103124
show_widget(args["featuresdir"], args["ifcfile"])
104125
else:
105126
run.run_tests(args)
127+
if args["report_after_run"]:
128+
reports.generate_report()
106129
print("# All tasks are complete :-)")

0 commit comments

Comments
 (0)