diff --git a/src/ifcbimtester/README.md b/src/ifcbimtester/README.md index 162b355806..30837779c8 100644 --- a/src/ifcbimtester/README.md +++ b/src/ifcbimtester/README.md @@ -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 +``` diff --git a/src/ifcbimtester/bimtester/features/dummy.feature b/src/ifcbimtester/bimtester/features/dummy.feature deleted file mode 100644 index 59da694397..0000000000 --- a/src/ifcbimtester/bimtester/features/dummy.feature +++ /dev/null @@ -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 diff --git a/src/ifcbimtester/bimtester/features/environment.py b/src/ifcbimtester/bimtester/features/environment.py index 0de0c9e08d..1089fef7e4 100644 --- a/src/ifcbimtester/bimtester/features/environment.py +++ b/src/ifcbimtester/bimtester/features/environment.py @@ -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, ) diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata.py b/src/ifcbimtester/bimtester/features/steps/ifcdata.py index ad5177aa56..9693719928 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata.py @@ -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 ) diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata_de.py b/src/ifcbimtester/bimtester/features/steps/ifcdata_de.py index bd02372155..c36af66e98 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata_de.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata_de.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata_fr.py b/src/ifcbimtester/bimtester/features/steps/ifcdata_fr.py index c1d3b6526c..74d4cfd5ac 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata_fr.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata_fr.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata_it.py b/src/ifcbimtester/bimtester/features/steps/ifcdata_it.py index 60222112bc..435322227f 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata_it.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata_it.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata_methods.py b/src/ifcbimtester/bimtester/features/steps/ifcdata_methods.py deleted file mode 100644 index d42e563de4..0000000000 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata_methods.py +++ /dev/null @@ -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) - ) diff --git a/src/ifcbimtester/bimtester/features/steps/ifcdata_nl.py b/src/ifcbimtester/bimtester/features/steps/ifcdata_nl.py index e2491085e2..de9919e319 100644 --- a/src/ifcbimtester/bimtester/features/steps/ifcdata_nl.py +++ b/src/ifcbimtester/bimtester/features/steps/ifcdata_nl.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/project_setup.py b/src/ifcbimtester/bimtester/features/steps/project_setup.py index 8fbf128d52..d51a55ab40 100644 --- a/src/ifcbimtester/bimtester/features/steps/project_setup.py +++ b/src/ifcbimtester/bimtester/features/steps/project_setup.py @@ -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") diff --git a/src/ifcbimtester/bimtester/features/steps/utils.py b/src/ifcbimtester/bimtester/features/steps/utils.py index f3078f8e61..3e4032d463 100644 --- a/src/ifcbimtester/bimtester/features/steps/utils.py +++ b/src/ifcbimtester/bimtester/features/steps/utils.py @@ -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 diff --git a/src/ifcbimtester/bimtester/guiwidget.py b/src/ifcbimtester/bimtester/guiwidget.py index 707d015bcc..e5f24244b9 100644 --- a/src/ifcbimtester/bimtester/guiwidget.py +++ b/src/ifcbimtester/bimtester/guiwidget.py @@ -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): diff --git a/src/ifcbimtester/bimtester/ifc.py b/src/ifcbimtester/bimtester/ifc.py new file mode 100644 index 0000000000..922d975888 --- /dev/null +++ b/src/ifcbimtester/bimtester/ifc.py @@ -0,0 +1,3 @@ +class IfcStore: + path = "" + file = None diff --git a/src/ifcbimtester/bimtester/lang.py b/src/ifcbimtester/bimtester/lang.py new file mode 100644 index 0000000000..60caf519cc --- /dev/null +++ b/src/ifcbimtester/bimtester/lang.py @@ -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 diff --git a/src/ifcbimtester/bimtester/reports.py b/src/ifcbimtester/bimtester/reports.py index a1212c645d..21ddc0e016 100644 --- a/src/ifcbimtester/bimtester/reports.py +++ b/src/ifcbimtester/bimtester/reports.py @@ -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"], "" + a["value"] + "") - 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"], "" + a["value"] + "") + 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"), + } diff --git a/src/ifcbimtester/bimtester/resources/reports/template.html b/src/ifcbimtester/bimtester/resources/reports/template.html index aaede69447..98761cc5e1 100644 --- a/src/ifcbimtester/bimtester/resources/reports/template.html +++ b/src/ifcbimtester/bimtester/resources/reports/template.html @@ -1,5 +1,5 @@ - +
@@ -32,8 +32,8 @@{{time}} {{file_name}}
{{#description}}
@@ -46,10 +46,10 @@
- {{#is_success}}{{tr_success}}{{/is_success}}{{^is_success}}{{tr_failure}}{{/is_success}}
- {{tr_tests_passed}}: {{total_passes}} / {{total_steps}} ({{pass_rate}}%)
+ {{#is_success}}{{_success}}{{/is_success}}{{^is_success}}{{_failure}}{{/is_success}}
+ {{_tests_passed}}: {{total_passes}} / {{total_steps}} ({{pass_rate}}%)
- {{tr_duration}}: {{time}}s
+ {{_duration}}: {{time}}s
{{name}}
@@ -72,7 +72,7 @@
diff --git a/src/ifcbimtester/bimtester/run.py b/src/ifcbimtester/bimtester/run.py
index dfe15bac36..0d80900a6c 100644
--- a/src/ifcbimtester/bimtester/run.py
+++ b/src/ifcbimtester/bimtester/run.py
@@ -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
diff --git a/src/ifcbimtester/cli.py b/src/ifcbimtester/cli.py
new file mode 100755
index 0000000000..6cfc02c6a9
--- /dev/null
+++ b/src/ifcbimtester/cli.py
@@ -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 :-)")
diff --git a/src/ifcbimtester/gui.py b/src/ifcbimtester/gui.py
new file mode 100644
index 0000000000..dfbbc6237f
--- /dev/null
+++ b/src/ifcbimtester/gui.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+from bimtester.guiwidget import run
+
+
+run()
diff --git a/src/ifcbimtester/startbimtester.py b/src/ifcbimtester/startbimtester.py
deleted file mode 100644
index 08ec3d9d2d..0000000000
--- a/src/ifcbimtester/startbimtester.py
+++ /dev/null
@@ -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 :-)")