BIMTester now operates on single feature files, which heavily improves usability

This commit is contained in:
Dion Moult
2021-02-02 12:34:14 +11:00
parent 1f4df32d04
commit ac95fb1aa2
20 changed files with 401 additions and 978 deletions
+110 -17
View File
@@ -1,29 +1,122 @@
# BIMTester
### Packages to be installed
+ behave
+ pystache
+ ifcopenshell
BIMTester lets you specify a set of exchange requirements, and automatically check whether or not BIM data, typically an
IFC file, complies with the requirements. You can run it automatically from a server, integrate it to your own
application, or use a GUI. It can generate reports in various formats including:
* HTML
* JSON
* XUnit
* BCF
* Zoom Smart View
Your exchange requirements are written in plain language which you can use to communicate to project teams and include
in contracts. Multiple languages are supported. A series of exchange requirement templates are provided to get started,
but you can, and are encouraged to, write your own tailored to your project. An example of a requirement looks like
this:
### Start bimtester Gui from a shell
```
python3 ./bimtester.py -g
Feature: Project setup
In order to view the BIM data
As any interested stakeholder
We need an IFC file
Scenario: Receiving a file
* IFC data must use the IFC4 schema
```
### Create a binary out of the Python package
+ The commands needs to be updated
+ Unix:
`$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features:features" bimtester.py`
+ Windows:
`$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features;features" bimtester.py`
Languages supported include (in alphabetical order):
* Dutch
* English
* French
* German
* Italian
If you are not a developer, we highly recommend simply installing an integrated version of BIMTester.
* If you use Blender, install the [BlenderBIM Add-on](https://blenderbim.org)
* If you use FreeCAD, install the [FreeCAD BIMTester Workbench](https://github.com/bimtester/bimtesterfc)
If you are a developer, read on!
## Installation
The following packages are required for BIMTester to function:
* behave
* pystache
* ifcopenshell
* PySide2 (optional: needed for GUI)
The repository does not contain translation files. You can generate them as shown.
### Translation files
+ the binary mo translation files are not part of the repo
+ they might be needed to be recreated
+ use the following commands on Linux
```
cd YourIfcopenshellRepository/src/ifcbimtester/bimtester/locale
cd IfcOpenShell/src/ifcbimtester/bimtester/locale
pybabel compile -d .
```
## CLI Usage
BIMTester has a command line application. Check it out:
```
$ python cli.py -h
usage: cli.py [-h] [-a ACTION] [--advanced-arguments ADVANCED_ARGUMENTS] [-c]
-f FEATURE -i IFC [-p PATH] [-r REPORT] [--lang LANG]
Runs unit tests for BIM data
optional arguments:
-h, --help show this help message and exit
-a ACTION, --action ACTION
Action to perform, from run/purge
--advanced-arguments ADVANCED_ARGUMENTS
Specify arguments to Behave
-c, --console Show results in the console
-f FEATURE, --feature FEATURE
Specify a feature file to test
-i IFC, --ifc IFC Specify a ifc file
-p PATH, --path PATH Define a path for use in tests
-r REPORT, --report REPORT
Specify an output file for a HTML report
--lang LANG Specify a language
```
You can turn it into a regular command by symlinking it to your bin folder.
```
$ ln -s /path/to/IfcOpenShell/src/ifcbimtester/cli.py /usr/local/bin/bimtester
$ bimtester -h
```
To run a test, we need an IFC to check and a feature file filled with requirements. The feature file is plaintext. Feel
free to copy the minimal example above.
```
# To see output in the console
$ bimtester -i test.ifc -f test.feature -c
# Or, if you want to generate a HTML report
$ bimtester -i test.ifc -f test.feature -r report.html
```
```
python ./gui.py
```
## Create a binary out of the Python package
TODO: Check if this works
Unix:
```
$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features:features" bimtester.py
```
Windows:
```
$ pyinstaller --onefile --clean --icon=icon.ico --add-data "features;features" bimtester.py
```
@@ -1,3 +0,0 @@
# dummy file to be able to run the command to get a list of all steps
# the command has to be run in the parent directory of this one
# behave --steps-catalog
@@ -1,28 +1,28 @@
import os
from behave.model import Scenario
from logfile import create_logfile
from logfile import append_logfile
from zoom_smart_view import append_zoom_smartview
from zoom_smart_view import create_zoom_smartview
from bimtester.ifc import IfcStore
from bimtester.lang import switch_locale
this_path = os.path.dirname(os.path.realpath(__file__))
def before_all(context):
# get from userdata
userdata = context.config.userdata
context.localedir = userdata.get("localedir")
context.ifcfile = userdata["ifcfile"]
context.ifcbasename = os.path.basename(
os.path.splitext(context.ifcfile)[0]
if context.config.lang:
switch_locale(userdata.get("localedir"), context.config.lang)
context.ifc_path = userdata["ifc"]
context.ifc_basename = os.path.basename(
os.path.splitext(context.ifc_path)[0]
)
# do not break after a failed scenario
# https://community.osarch.org/discussion/comment/3328/#Comment_3328
continue_after_failed = userdata.getbool(
"runner.continue_after_failed_step", True
)
@@ -37,21 +37,21 @@ def before_all(context):
# set up log file
context.thelogfile = os.path.join(
context.outpath,
context.ifcbasename + ".log"
context.ifc_basename + ".log"
)
create_logfile(
context.thelogfile,
context.ifcbasename,
context.ifc_basename,
)
# set up smart view file
context.smview_file = os.path.join(
context.outpath,
context.ifcbasename + ".bcsv"
context.ifc_basename + ".bcsv"
)
create_zoom_smartview(
context.smview_file,
context.ifcbasename,
context.ifc_basename,
)
@@ -1,20 +1,16 @@
import gettext # noqa
import gettext
from behave import given
from behave import step
import ifcdata_methods as idm
from utils import IfcFile
from utils import switch_locale
the_lang = "en"
from bimtester.ifc import IfcStore
from bimtester.lang import _
@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)
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"
@@ -28,25 +24,11 @@ def step_impl(context, file):
assert False, f"The file {file} could not be loaded"
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@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):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
real_schema = IfcStore.file.schema
assert real_schema == schema, _("We expected a schema of {} but instead got {}").format(schema, real_schema)
@step('The IFC file "{file}" is exempt from being provided')
def step_impl(context, file):
@@ -61,41 +43,43 @@ def step_impl(context, reason):
@step("The IFC file must be exported by application full name {fullname}")
def step_impl(context, fullname):
real_fullname = IfcFile.get().by_type("IfcApplication")[0].ApplicationFullName
assert real_fullname == fullname , (
real_fullname = IfcStore.file.by_type("IfcApplication")[0].ApplicationFullName
assert real_fullname == fullname, (
"The IFC file was not exported by application full name {} "
"instead it was exported by application full name {}"
.format(fullname, real_fullname)
"instead it was exported by application full name {}".format(fullname, real_fullname)
)
@step("The IFC file must be exported by application identifier {identifier}")
def step_impl(context, identifier):
real_identifier = IfcFile.get().by_type("IfcApplication")[0].ApplicationIdentifier
assert real_identifier == identifier , (
"The IFC file was not exported by application identifier {} "
"instead it was exported by identifier {}"
.format(identifier, real_identifier)
real_identifier = IfcStore.file.by_type("IfcApplication")[0].ApplicationIdentifier
assert (
real_identifier == identifier
), "The IFC file was not exported by application identifier {} " "instead it was exported by identifier {}".format(
identifier, real_identifier
)
@step("The IFC file must be exported by the application version {version}")
def step_impl(context, version):
real_version = IfcFile.get().by_type("IfcApplication")[0].Version
assert real_version == version , (
"The IFC file was not exported by application version {} "
"instead it was exported by version {}"
.format(version, real_version)
real_version = IfcStore.file.by_type("IfcApplication")[0].Version
assert (
real_version == version
), "The IFC file was not exported by application version {} " "instead it was exported by version {}".format(
version, real_version
)
@step("IFC data header must have a file description of {header_file_description} such as the new Allplan IFC exporter creates it")
@step(
"IFC data header must have a file description of {header_file_description} such as the new Allplan IFC exporter creates it"
)
def step_impl(context, header_file_description):
is_header_file_description = IfcFile.get().wrapped_data.header.file_description.description
assert str(is_header_file_description) == header_file_description , (
"The file was not exported by the new ifc exporter in Allplan. File description header: {}"
.format(is_header_file_description)
is_header_file_description = IfcStore.file.wrapped_data.header.file_description.description
assert (
str(is_header_file_description) == header_file_description
), "The file was not exported by the new ifc exporter in Allplan. File description header: {}".format(
is_header_file_description
)
@@ -1,19 +1,6 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "de"
@given("Die IFC-Datei wurde durch einen Startparameter zur Verfügung gestellt")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("Die IFC-Daten müssen das {schema} Schema benutzen")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
context.execute_steps(f"* IFC data must use the {schema} schema")
@@ -1,22 +1,6 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "fr"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("Les données IFC doivent utiliser le schéma {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
context.execute_steps(f"* IFC data must use the {schema} schema")
@@ -1,19 +1,6 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "it"
@given("Il file IFC è stato fornito attraverso un argumento")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
@step("I dati IFC devono seguire lo schema {schema}")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
context.execute_steps(f"* IFC data must use the {schema} schema")
@@ -1,19 +0,0 @@
from utils import IfcFile
def provide_ifcfile_by_argument(context):
try:
IfcFile.load(context.config.userdata.get("ifcfile"))
except:
assert False, (
_("The IFC {} file could not be loaded")
.format(context.config.userdata.get('ifcfile'))
)
def has_ifcdata_specific_schema(context, target_schema):
real_schema = IfcFile.get().schema
assert real_schema == target_schema, (
_("We expected a schema of {} but instead got {}")
.format(target_schema, real_schema)
)
@@ -1,22 +1,6 @@
from behave import step
import ifcdata_methods as idm
from utils import switch_locale
the_lang = "nl"
"""
# TODO the next line needs translation
@given("The IFC file has been provided through an argument")
def step_impl(context):
switch_locale(context.localedir, the_lang)
idm.provide_ifcfile_by_argument(context)
"""
@step("IFC-gegevens moeten het {schema} -schema gebruiken")
def step_impl(context, schema):
switch_locale(context.localedir, the_lang)
idm.has_ifcdata_specific_schema(context, schema)
context.execute_steps(f"* IFC data must use the {schema} schema")
@@ -2,36 +2,37 @@ from behave import step
from utils import assert_attribute
from utils import IfcFile
from bimtester.ifc import IfcStore
@step("The project must have an identifier of {guid}")
def step_impl(context, guid):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "GlobalId", guid)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "GlobalId", guid)
@step('The project name, code, or short identifier must be "{value}"')
def step_impl(context, value):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Name", value)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Name", value)
@step('The project must have a longer form name of "{value}"')
def step_impl(context, value):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "LongName", value)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "LongName", value)
@step('The project must be described as "{value}"')
def step_impl(context, value):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Description", value)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Description", value)
@step('The project must be categorised under "{value}"')
def step_impl(context, value):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "ObjectType", value)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "ObjectType", value)
@step('The project must contain information about the "{value}" phase')
def step_impl(context, value):
assert_attribute(IfcFile.get().by_type("IfcProject")[0], "Phase", value)
assert_attribute(IfcStore.file.by_type("IfcProject")[0], "Phase", value)
@step("The project must contain 3D geometry representing the shape of objects")
@@ -4,7 +4,6 @@ import ifcopenshell.express
import ifcopenshell.util
import ifcopenshell.util.element
class IfcFile(object):
file = None
bookmarks = {}
@@ -14,7 +13,7 @@ class IfcFile(object):
cls.file = ifcopenshell.open(path)
if not cls.file:
assert False
@classmethod
def load_schema(cls, path=None):
schema = ifcopenshell.express.parse(path)
@@ -32,7 +31,7 @@ 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())
@@ -108,60 +107,36 @@ def assert_elements(
message_all_falseelems,
message_some_falseelems,
message_no_elems,
parameter=None
parameter=None,
):
if elemcount > 0 and falsecount == 0:
return # Test OK
elif elemcount == 0:
assert False, (
message_no_elems.format(
ifc_class=ifc_class
)
)
assert False, message_no_elems.format(ifc_class=ifc_class)
elif falsecount == elemcount:
if parameter is None:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class
)
)
assert False, message_all_falseelems.format(elemcount=elemcount, ifc_class=ifc_class)
else:
assert False, (
message_all_falseelems.format(
elemcount=elemcount,
ifc_class=ifc_class,
parameter=parameter
)
)
assert False, message_all_falseelems.format(elemcount=elemcount, ifc_class=ifc_class, parameter=parameter)
elif falsecount > 0 and falsecount < elemcount:
if parameter is None:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
)
assert False, message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
)
else:
assert False, (
message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
parameter=parameter
)
assert False, message_some_falseelems.format(
falsecount=falsecount,
elemcount=elemcount,
ifc_class=ifc_class,
falseelems=falseelems,
parameter=parameter,
)
else:
assert False, _("Error in falsecount, something went wrong.")
def switch_locale(locale_dir, locale_id="en"):
newlang = gettext.translation(
"messages",
localedir=locale_dir,
languages=[locale_id]
)
newlang.install()
pass
+23 -138
View File
@@ -1,71 +1,33 @@
# TODO: all args should be passed to the gui,
# either pass them further to behave or make it possible to edit them before
# TODO: if browse widgets will be canceled, last QLineEdit should be restored
# TODO: keep path or file if in browse widget canceled
# TODO: make a frame around features direcory chooser and
# use features path from ifc button
import os
import sys
import bimtester.run
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
from .run import run_all
def run():
app = QtWidgets.QApplication(sys.argv)
form = GuiWidgetBimTester()
form.show()
sys.exit(app.exec_())
class GuiWidgetBimTester(QtWidgets.QWidget):
def __init__(
self,
featurespath="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
def __init__(self, args=[]):
super(GuiWidgetBimTester, self).__init__()
user_path = os.path.expanduser("~")
# get features dir
# print(featurespath)
if not os.path.isdir(featurespath):
featurespath = user_path
# get ifc file
# print(ifcfile)
if not os.path.isfile(ifcfile):
ifcfile = user_path
self.initial_featurespath = featurespath
self.initial_ifcfile = ifcfile
self.get_featurepath_from_ifcpath = get_featurepath_from_ifcpath
self.args = args
# print(self.initial_featurespath)
# print(self.initial_ifcfile)
# print(self.get_featurepath_from_ifcpath)
# init ui
self._setup_ui()
def __del__(self,):
# need as fix for qt event error
# http://forum.freecadweb.org/viewtopic.php?f=18&t=10732&start=10#p86493
# http://forum.freecadweb.org/viewtopic.php?f=18&t=10732&start=10#p86493
def __del__(self):
return
def _setup_ui(self):
# a lot code is taken from FreeCAD FEM solver frame work task panel
# https://forum.freecadweb.org/viewtopic.php?f=10&t=51419
# use a browse button, and a line edit
# the browse button opens a file dialog, which will set the line edit
# icon
# print(__file__)
package_path = os.path.dirname(os.path.realpath(__file__))
iconpath = os.path.join(
package_path, "resources", "icons", "bimtester.ico"
)
iconpath = os.path.join(package_path, "resources", "icons", "bimtester.ico")
"""
# as svg
# https://stackoverflow.com/a/35138314
@@ -78,6 +40,7 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
#)
#theicon.sizeHint()
"""
# as pixmap
theicon = QtWidgets.QLabel(self)
iconpixmap = QtGui.QPixmap(iconpath)
@@ -87,7 +50,6 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# ifc file
_ifcfile_label = QtWidgets.QLabel("IFC file", self)
self.ifcfile_text = QtWidgets.QLineEdit()
self.set_ifcfile(self.initial_ifcfile)
_ifcfile_browse_btn = QtWidgets.QToolButton()
_ifcfile_browse_btn.setText("...")
_ifcfile_browse_btn.clicked.connect(self.select_ifcfile)
@@ -95,36 +57,20 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
# feature files path
# use a layout with a frame and a title, see solver framework tp
# beside button
ffifc_str = (
"Feature files in a directory 'features' beside the IFC file."
)
ffifc_str = "Feature files in a directory 'features' beside the IFC file."
featuredirfromifc_label = QtWidgets.QLabel(ffifc_str, self)
self.featuredirfromifc_cb = QtWidgets.QCheckBox(self)
self.featuredirfromifc_cb.stateChanged.connect(
self.featuredirfromifc_clicked
)
# path browser and line edit
_ffdir_str = (
"Feature files directory. "
"'features' directory has to be in there."
)
_ffdir_str = "Feature files directory. " "'features' directory has to be in there."
_featurefilesdir_label = QtWidgets.QLabel(_ffdir_str, self)
self.featurefilesdir_text = QtWidgets.QLineEdit()
self.set_featurefilesdir(self.initial_featurespath)
self.feafilesdir_browse_btn = QtWidgets.QToolButton()
self.feafilesdir_browse_btn.setText("...")
self.feafilesdir_browse_btn.clicked.connect(
self.select_featurefilesdir
)
self.feafilesdir_browse_btn.clicked.connect(self.select_featurefilesdir)
# buttons
self.run_button = QtWidgets.QPushButton(
QtGui.QIcon.fromTheme("document-new"), "Run"
)
self.close_button = QtWidgets.QPushButton(
QtGui.QIcon.fromTheme("window-close"), "Close"
)
self.run_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("document-new"), "Run")
self.close_button = QtWidgets.QPushButton(QtGui.QIcon.fromTheme("window-close"), "Close")
self.run_button.clicked.connect(self.run_bimtester)
self.close_button.clicked.connect(self.close_widget)
_buttons = QtWidgets.QHBoxLayout()
@@ -136,7 +82,6 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.addWidget(theicon, 1, 0, alignment=QtCore.Qt.AlignRight)
layout.addWidget(featuredirfromifc_label, 2, 0)
layout.addWidget(self.featuredirfromifc_cb, 2, 1)
layout.addWidget(_featurefilesdir_label, 3, 0)
layout.addWidget(self.featurefilesdir_text, 4, 0)
@@ -153,17 +98,8 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
layout.setRowStretch(0, 10)
self.setLayout(layout)
# set button state, needs to be here after gui has beed set up
self.featuredirfromifc_cb.setChecked(self.get_featurepath_from_ifcpath)
# **********************************************************
def select_ifcfile(self):
# print(self.get_ifcfile())
# print(os.path.isfile(self.get_ifcfile()))
ifcfile = QtWidgets.QFileDialog.getOpenFileName(
self,
dir=self.get_ifcfile()
)[0]
ifcfile = QtWidgets.QFileDialog.getOpenFileName(self, dir=self.get_ifcfile())[0]
self.set_ifcfile(ifcfile)
def set_ifcfile(self, a_file):
@@ -172,26 +108,13 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def get_ifcfile(self):
return self.ifcfile_text.text()
def featuredirfromifc_clicked(self):
if self.featuredirfromifc_cb.isChecked() is True:
self.set_featurefilesdir("")
self.featurefilesdir_text.setEnabled(False)
self.feafilesdir_browse_btn.setEnabled(False)
else:
self.set_featurefilesdir(self.initial_featurespath)
self.featurefilesdir_text.setEnabled(True)
self.feafilesdir_browse_btn.setEnabled(True)
def select_featurefilesdir(self):
thedir = self.featurefilesdir_text.text()
# print(thedir)
# print(os.path.isdir(thedir))
# hidden directories are only shown if the option is set
features_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
caption="Choose features directory ...",
dir=thedir,
options=QtWidgets.QFileDialog.HideNameFilterDetails
options=QtWidgets.QFileDialog.HideNameFilterDetails,
)
self.set_featurefilesdir(features_path)
@@ -201,46 +124,11 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
def get_featurefilesdir(self):
return self.featurefilesdir_text.text()
# **********************************************************
def run_bimtester(self):
print("Run BIMTester by the GUI")
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
# get features dir
if self.featuredirfromifc_cb.isChecked() is True:
ifcfile = self.get_ifcfile()
print(
"Make sure the feature files are beside "
"the ifc file in a directory named 'features'."
)
if ifcfile == "":
# os.path.realpath("") would return the cmd dir and not ""
print(
"No ifcfile given, "
"thus features files path will be set to ''."
)
the_features_path = ""
elif os.path.isfile(ifcfile) is not True:
# if ifcfile does not exist set features path to ""
print(
"The ifcfile does not exist, "
"thus features files path will be set to ''."
)
the_features_path = ""
else:
ifcfilepath = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifcfilepath):
the_features_path = ifcfilepath
else:
print(
"ifcfilepath does not exist, "
"thus features files path will be set to ''."
"this shold never happen, please debug. "
)
the_features_path = ""
else:
the_features_path = self.get_featurefilesdir()
the_features_path = self.get_featurefilesdir()
print(the_features_path)
# get ifc file
@@ -252,14 +140,11 @@ class GuiWidgetBimTester(QtWidgets.QWidget):
patched_args["featuresdir"] = the_features_path
patched_args["ifcfile"] = the_ifcfile
# run bimtester
status = run_all(patched_args)
print(status)
bimtester.run.TestRunner("file.ifc").run({})
QtWidgets.QApplication.restoreOverrideCursor()
def close_widget(self):
print("Close BIMTester Gui")
self.close()
def closeEvent(self, ev):
+3
View File
@@ -0,0 +1,3 @@
class IfcStore:
path = ""
file = None
+15
View File
@@ -0,0 +1,15 @@
import gettext
translation = None
def _(message):
if translation:
return translation(message)
return message
def switch_locale(locale_dir, locale_id="en"):
global translation
newlang = gettext.translation("messages", localedir=locale_dir, languages=[locale_id])
newlang.install()
translation = newlang.gettext
+96 -135
View File
@@ -1,58 +1,26 @@
import datetime
import gettext # noqa
import json
import os
import pystache
from .features.steps.utils import switch_locale
from bimtester.lang import _
def generate_report(
report_dir=".",
use_report_folder=True,
report_file_name="report.json",
html_template_file_path="",
report_file=""
):
class ReportGenerator:
def __init__(self):
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
self.base_path = sys._MEIPASS
except Exception:
self.base_path = os.path.dirname(os.path.realpath(__file__))
# TODO use far less parameter
# to be discussed with other devs
def generate(self, report_json, output_file):
print("# Generating HTML reports.")
print("# Generating HTML reports now.")
report = json.loads(open(report_json).read())
for feature in report:
self.generate_feature_report(feature, output_file)
# get locale path
localedir = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"locale"
)
# get html template path
report_template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"resources",
"reports"
)
if html_template_file_path:
report_template_path = html_template_file_path
# get report file and report dir
if report_file:
report_file = report_file
report_dir = os.path.dirname(report_file)
else:
if use_report_folder:
report_dir = os.path.join(report_dir, "report")
report_file = os.path.join(report_dir, report_file_name)
# print(report_file)
if not os.path.isdir(report_dir):
return print("Report directory does not exist.")
if not os.path.isfile(report_file):
return print("Report file does not exist.")
# read json report and create html report for each feature
report = json.loads(open(report_file).read())
for feature in report:
def generate_feature_report(self, feature, output_file):
file_name = os.path.basename(feature["location"]).split(":")[0]
data = {
"file_name": file_name,
@@ -62,108 +30,101 @@ def generate_report(
"is_success": feature["status"] == "passed",
"scenarios": [],
}
if "elements" not in feature:
if "status" in feature and feature["status"] == "skipped":
print("Feature was skipped. No html report will be created.")
else:
print("For a unknown reason no html report well be created.")
# happens if the feature file does not consist of any valid Scenario
continue
return
for scenario in feature["elements"]:
steps = []
total_duration = 0
if len(scenario["steps"]) == 0:
print(
"Scenario '{}' in feature '{}' has no steps. "
"Thus skipped in report."
.format(scenario["name"], feature["name"])
)
continue
for step in scenario["steps"]:
if "result" in step:
total_duration += step["result"]["duration"]
name = step["name"]
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:
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
step["result"]["error_message"] = "This requirement has not yet been specified."
steps.append(
{
"name": name,
"time": round(step["result"]["duration"], 2),
"is_success": step["result"]["status"] == "passed",
"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"],
}
)
total_passes = len([s for s in steps if s["is_success"] is True])
total_steps = len(steps)
pass_rate = round((total_passes / total_steps) * 100)
data["scenarios"].append(
{
"name": scenario["name"],
# on behave < 1.2.6 there is no 'status' thus report fails
"is_success": scenario["status"] == "passed",
"time": round(total_duration, 2),
"steps": steps,
"total_passes": total_passes,
"total_steps": total_steps,
"pass_rate": pass_rate,
}
)
scenario_data = self.process_scenario(scenario)
if scenario_data:
data["scenarios"].append(scenario_data)
data["total_passes"] = sum([s["total_passes"] for s in data["scenarios"]])
data["total_steps"] = sum([s["total_steps"] for s in data["scenarios"]])
data["pass_rate"] = round((data["total_passes"] / data["total_steps"]) * 100)
# translate report
# json.dump(mydict, myfile, indent=4)
# workaround for retrieving the feature file language
print(feature["keyword"])
if feature["keyword"] == "Feature":
switch_locale(localedir, "en")
elif feature["keyword"] == "Funktionalität":
switch_locale(localedir, "de")
elif feature["keyword"] == "Fonctionnalité":
switch_locale(localedir, "fr")
elif feature["keyword"] == "Funzionalità":
switch_locale(localedir, "it")
elif feature["keyword"] == "Functionaliteit":
switch_locale(localedir, "nl")
else:
# standard English
switch_locale(localedir, "en")
strings_report = get_html_template_strings()
# print(strings_report)
data.update(strings_report)
# print(data)
data.update(self.get_template_strings())
html_report = os.path.join(report_dir, "{}.html".format(file_name))
html_tmpl = os.path.join(report_template_path, "template.html")
with open(html_report, "w", encoding="utf8") as out:
with open(html_tmpl, encoding="utf8") as template:
with open(output_file, "w", encoding="utf8") as out:
with open(
os.path.join(self.base_path, "resources", "reports", "template.html"), encoding="utf8"
) as template:
out.write(pystache.render(template.read(), data))
def process_scenario(self, scenario):
if len(scenario["steps"]) == 0:
print("Scenario '{}' in feature '{}' has no steps.".format(scenario["name"], feature["name"]))
return
def get_html_template_strings():
steps = []
total_duration = 0
return {
"tr_lang": _("en"),
"tr_success": _("Success"),
"tr_failure": _("Failure"),
"tr_tests_passed": _("Tests passed"),
"tr_duration": _("Duration"),
"tr_auditing": _("OpenBIM auditing is a feature of"),
"tr_and": _("and")
}
for step in scenario["steps"]:
step_data = self.process_step(step)
total_duration += step_data["time_raw"]
steps.append(step_data)
total_passes = len([s for s in steps if s["is_success"] is True])
total_steps = len(steps)
pass_rate = round((total_passes / total_steps) * 100)
return {
"name": scenario["name"],
# on behave < 1.2.6 there is no 'status' thus report fails
"is_success": scenario["status"] == "passed",
"time": round(total_duration, 2),
"steps": steps,
"total_passes": total_passes,
"total_steps": total_steps,
"pass_rate": pass_rate,
}
def process_step(self, step):
name = step["name"]
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:
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
step["result"]["error_message"] = "This requirement has not yet been specified."
data = {
"name": name,
"time_raw": step["result"]["duration"],
"time": round(step["result"]["duration"], 2),
"is_success": step["result"]["status"] == "passed",
"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"],
}
# TODO: there is probably a better way of doing this
if isinstance(data["error_message"], list):
data["error_message"] = data["error_message"][1]
return data
def get_template_strings(self):
return {
"_lang": _("en"),
"_success": _("Success"),
"_failure": _("Failure"),
"_tests_passed": _("Tests passed"),
"_duration": _("Duration"),
"_auditing": _("OpenBIM auditing is a feature of"),
"_and": _("and"),
}
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang={{tr_lang}}>
<html lang={{_lang}}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -32,8 +32,8 @@
<h1>{{name}}</h1>
<p><strong>{{time}} {{file_name}}</strong></p>
<hr>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<br />
<p class="description">
{{#description}}
@@ -46,10 +46,10 @@
<section>
<h2>{{name}}</h2>
<p>
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}</span>
{{tr_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="{{#is_success}}success{{/is_success}}{{^is_success}}failure{{/is_success}}">{{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}</span>
{{_tests_passed}}: <strong>{{total_passes}} / {{total_steps}}</strong> ({{pass_rate}}%)
<span class="time">
{{tr_duration}}: {{time}}s
{{_duration}}: {{time}}s
</span>
</p>
<ol>
@@ -72,7 +72,7 @@
<hr>
<footer>
<p>
{{tr_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{tr_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
{{_auditing}} <a href="https://blenderbim.org/">BlenderBIM</a> {{_and}} <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
</p>
</footer>
</body>
+41 -326
View File
@@ -1,343 +1,58 @@
import behave.formatter.pretty # Needed for pyinstaller to package it
import os
import shutil
import sys
import shutil
import tempfile
import webbrowser
import ifcopenshell
import behave.formatter.pretty # Needed for pyinstaller to package it
from bimtester.ifc import IfcStore
from bimtester.lang import switch_locale
from behave import __version__ as behave_version
from behave.__main__ import main as behave_main
# TODO: if the ifc file name or path contains special character
# like German Umlaute behave gives an error
class TestRunner:
def __init__(self, ifc_path, ifc=None):
IfcStore.path = ifc_path
IfcStore.file = ifc if ifc else ifcopenshell.open(ifc_path)
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
self.base_path = sys._MEIPASS
except Exception:
self.base_path = os.path.dirname(os.path.realpath(__file__))
# get bimtester source code module path
bimtester_path = os.path.dirname(os.path.realpath(__file__))
# print(bimtester_path)
locale_path = os.path.join(bimtester_path, "locale")
self.locale_path = os.path.join(self.base_path, "locale")
def run(self, args):
print("# Run tests.")
tmpdir = tempfile.mkdtemp()
features_path = os.path.join(tmpdir, "features")
report_json = os.path.join(tmpdir, "report.json")
shutil.copytree(os.path.join(self.base_path, "features"), features_path)
shutil.copy(args["feature"], features_path)
behave_main(self.get_behave_args(args, features_path, report_json))
print("# All tests are finished.")
return report_json
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.dirname(os.path.realpath(__file__))
def get_resource_path(relative_path):
return os.path.join(base_path, relative_path)
def run_tests(args):
print("# Run tests.")
report_file = os.path.join("report", "report.json")
if "copyintemprun" in args and args["copyintemprun"] is True:
print("copyintemprun")
is_copyintemprun = True
args, copy_base_path = copy_intmp_tests(args)
features_path = os.path.join(copy_base_path, "features")
report_file = os.path.join(copy_base_path, report_file)
else:
print("No copyintemprun")
is_copyintemprun = False
if not get_features(args):
print("No features could be found to check.")
return False
features_path = get_resource_path("features")
# get behave args
behave_args = get_behave_args(args, features_path, report_file)
# run tests
if behave_args != []:
run_behave(behave_args)
else:
print("Error, not able to run behave because of empty behave args.")
return False
if is_copyintemprun is True:
return report_file
else:
return True
def get_behave_args(args, features_path, report_file):
if os.path.isdir(features_path):
def get_behave_args(self, args, features_path, report_json):
behave_args = [features_path]
else:
return []
if os.path.isdir(locale_path):
behave_args.extend([
# path for translation files
# next two lines are one arg
"--define",
"localedir={}".format(locale_path)
])
else:
print(
"Error, translation locals path '{}' does not exist."
.format(locale_path)
)
behave_args.extend(["--define", "localedir={}".format(self.locale_path)])
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
if args["advanced_arguments"]:
behave_args.extend(args["advanced_arguments"].split())
if args["ifcfile"]:
behave_args.extend([
# next two lines are one arg
"--define",
"ifcfile={}".format(args["ifcfile"])
])
if args["ifc"]:
behave_args.extend(["--define", "ifc={}".format(args["ifc"])])
if args["path"]:
behave_args.extend([
# next two lines are one arg
"--define",
"path={}".format(args["path"])
])
if args["path"]:
behave_args.extend(["--define", "path={}".format(args["path"])])
if not args["console"]:
behave_args.extend([
# redirect prints in step methods
# if step fails some output is catched, thus might not be printed
if args["lang"]:
behave_args.extend(["--lang={}".format(args["lang"])])
if not args["console"]:
# https://github.com/behave/behave/issues/346
"--no-capture",
# next two lines are one arg
"--format",
"json.pretty",
# report file, if relative, than relative to current shell path
# next two lines are one arg
"--outfile",
report_file,
])
behave_args.extend(["--no-capture", "--format", "json.pretty", "--outfile", report_json])
return behave_args
def run_behave(behave_args):
from json import dumps
print(dumps(behave_args, indent=4))
from behave.__main__ import main as behave_main
behave_main(behave_args)
print("# All tests are finished.")
return True
def get_features(args):
# current_path = os.path.abspath(".")
features_dir = get_resource_path("features")
for f in os.listdir(features_dir):
if f.endswith(".feature"):
os.remove(os.path.join(features_dir, f))
if args["feature"]:
shutil.copyfile(
args["feature"],
os.path.join(
get_resource_path("features"),
os.path.basename(args["feature"])
)
)
return True
if os.path.exists("features"):
shutil.copytree("features", get_resource_path("features"))
return True
has_features = False
for f in os.listdir("."):
if not f.endswith(".feature"):
continue
if args["feature"] and args["feature"] != f:
continue
has_features = True
shutil.copyfile(
f,
os.path.join(get_resource_path("features"), os.path.basename(f))
)
return has_features
"""
# clean logs to be able to run tests
# once again but on another building model and in another directory
# somehow does not work, thus test will be run in the same directory
# on each new run, directory will be deleted before each new run
# https://github.com/behave/behave/issues/871
# run bimtester
# copy manually this code, run bimtester again,
# does not work on two directories
from behave.runner_util import reset_runtime
reset_runtime()
"""
def copy_intmp_tests(args={}):
print("# Copy features and steps to temp.")
from behave import __version__ as behave_version
# https://github.com/behave/behave/issues/871
if behave_version == "1.2.5":
print(
"At least behave version 1.2.6 is needed, but version {} found."
.format(behave_version)
)
return False
# print(args)
# get the features_path, the dir where the feature files to test are in
if ("featuresdir" in args and args["featuresdir"] != ""):
is_features = True
the_features_path = os.path.join(args["featuresdir"], "features")
if not os.path.isdir(the_features_path):
print(
"Error, the features directory '{}' does not exist."
.format(the_features_path)
)
return False
else:
is_features = False
# get ifc path and ifc filename
if ("ifcfile" in args and args["ifcfile"] != ""):
is_ifcfile = True
ifcfile = args["ifcfile"]
if os.path.isfile(ifcfile) is not True:
print("Error, the ifc file '{}' does not exist.".format(ifcfile))
return False
ifc_path = os.path.dirname(os.path.realpath(ifcfile))
if os.path.isdir(ifc_path) is False:
print("ifc path does not exist.")
return False
else:
is_ifcfile = False
# print(is_features)
# print(is_ifcfile)
if is_features is True and is_ifcfile is True:
print("features given, ifcfile given.")
elif is_features is False and is_ifcfile is True:
print("features given, ifcfile NOT given.")
# features = ifcfile directory
the_features_path = os.path.join(ifc_path, "features")
elif is_features is True and is_ifcfile is False:
print("features given, ifcfile NOT given.")
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
elif is_features is False and is_ifcfile is False:
print("features NOT given, ifcfile NOT given.")
# the current directory = features
# the ifcfile provided in the feature files is used
# TODO What will be passed as ifcfile arg?
print("Not yet implemented.")
return False
else:
print("Error: this should never happen, please debug.")
return False
# set up paths
# a unique temp path should not be used
# behave raises an ambiguous step exception
# copy_base_path = tempfile.mkdtemp()
# thus use the same path on every run
# but delete it if exists
copy_base_path = os.path.join(tempfile.gettempdir(), "bimtesterfc")
if os.path.isdir(copy_base_path):
from shutil import rmtree
rmtree(copy_base_path) # fails on read only files
if os.path.isdir(copy_base_path):
print(
"Delete former beimtester run dir '{}' failed"
.format(copy_base_path)
)
return False
os.mkdir(copy_base_path)
copy_features_path = os.path.join(copy_base_path, "features")
# copy features path from bimtester source code
srccode_features_path = os.path.join(
bimtester_path,
"features",
)
# print(srccode_features_path)
if os.path.exists(srccode_features_path):
shutil.copytree(srccode_features_path, copy_features_path)
else:
print(
"Bimtester source code features directory {} not found."
.format(srccode_features_path)
)
return False
# copy features files
# print(the_features_path)
# print(copy_features_path)
# parameter dirs_exist_ok=True, from py 3.8
# if os.path.exists(the_features_path):
# shutil.copytree(
# the_features_path,
# copy_features_path,
# dirs_exist_ok=True
# )
# copy feature files
feature_files = os.listdir(the_features_path)
# print(feature_files)
for feature_file in feature_files:
cp_feature_file = os.path.join(copy_features_path, feature_file)
# print(feature_file)
# copy file
shutil.copyfile(
os.path.join(the_features_path, feature_file),
cp_feature_file
)
return args, copy_base_path
def run_all(args):
print("# Run all.")
# run bimtester
report_file = run_tests(args)
print(report_file)
# check if it worked out well
if report_file is False:
print("BIMTester behave tests returned False.")
return False
if not os.path.isfile(report_file):
print("Report directory does not exist. This should not happen. Debug")
return False
# create html report and open in webbrowser
from .reports import generate_report
generate_report(report_file=report_file)
# get the feature files
feature_files = os.listdir(
os.path.join(args["featuresdir"], "features")
)
# print(feature_files)
for ff in feature_files:
webbrowser.open(os.path.join(
os.path.dirname(report_file),
ff + ".html"
))
return True
return behave_args
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import os
import argparse
import bimtester.clean
import bimtester.reports
import bimtester.run
parser = argparse.ArgumentParser(description="Runs unit tests for BIM data")
parser.add_argument("-a", "--action", type=str, help="Action to perform, from run/purge", default="run")
parser.add_argument("--advanced-arguments", type=str, help="Specify arguments to Behave", default="")
parser.add_argument("-c", "--console", action="store_true", help="Show results in the console")
parser.add_argument("-f", "--feature", type=str, help="Specify a feature file to test", required=True)
parser.add_argument("-i", "--ifc", type=str, help="Specify a ifc file", required=True)
parser.add_argument("-p", "--path", type=str, help="Define a path for use in tests")
parser.add_argument("-r", "--report", type=str, help="Specify an output file for a HTML report")
parser.add_argument("--lang", type=str, help="Specify a language", default="")
args = vars(parser.parse_args())
if args["action"] == "run":
report_json = bimtester.run.TestRunner(args["ifc"]).run(args)
if args["report"]:
bimtester.reports.ReportGenerator().generate(report_json, args["report"])
elif args["action"] == "purge":
bimtester.clean.TestPurger().purge()
print("# All tasks are complete :-)")
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python3
from bimtester.guiwidget import run
run()
-163
View File
@@ -1,163 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
from bimtester import clean
from bimtester import reports
from bimtester import run
def show_widget(
features="",
ifcfile="",
get_featurepath_from_ifcpath=False,
args=[]
):
import sys
from PySide2 import QtWidgets
from bimtester.guiwidget import GuiWidgetBimTester
# Create the Qt Application
app = QtWidgets.QApplication(sys.argv)
# Create and show the form
form = GuiWidgetBimTester(
features,
ifcfile,
get_featurepath_from_ifcpath,
args
)
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
if __name__ == "__main__":
# TODO make similar to bash commands
# use - not _ in named args
parser = argparse.ArgumentParser(
description="Runs unit tests for BIM data"
)
parser.add_argument(
"-a",
"--advanced-arguments",
type=str,
help="Specify your own arguments to Python's Behave",
default=""
)
parser.add_argument(
"-c",
"--console",
action="store_true",
help="Show results in the console"
)
parser.add_argument(
"-d",
"--featuresdir",
type=str,
help=(
"Specify a features directory. This should contain "
"a directory named 'features' which contains all the "
"feature files."
),
default=""
)
parser.add_argument(
"-f",
"--feature",
type=str,
help="Specify a feature file to test",
default=""
)
parser.add_argument(
"-g",
"--gui",
action="store_true",
help=(
"Start the gui. The option t (copyintemprun) "
"is triggered automaticly."
)
)
parser.add_argument(
"-i",
"--ifcfile",
type=str,
help=(
"Specify a ifc file."
),
default=""
)
parser.add_argument(
"-p",
"--purge",
action="store_true",
help="Purge tests of deleted elements"
)
parser.add_argument(
"-path",
"--path",
type=str,
help=(
"Specify a path to prepend to feature and ifc file"
),
default=""
)
parser.add_argument(
"-r",
"--report",
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(
"-t",
"--copyintemprun",
action="store_true",
help=(
"Copy steps and feature files into a temporary directory "
"and run bimtester with them."
)
)
args = vars(parser.parse_args())
from json import dumps
print(dumps(args, indent=4))
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"]:
reports.generate_report()
elif args["gui"]:
args["copyintemprun"] = True
fea = args["featuresdir"]
ifc = args["ifcfile"]
if fea != "" and ifc != "":
show_widget(fea, ifc, False, args)
elif fea == "" and ifc != "":
show_widget(fea, ifc, True, args)
elif args["copyintemprun"]:
run.run_tests(args)
# TODO merge with else, but do not forget the tmp dir is not known
else:
run.run_tests(args)
if args["report_after_run"]:
reports.generate_report()
print("# All tasks are complete :-)")