mirror of https://github.com/GNOME/gimp.git
628 lines
24 KiB
Python
Executable File
628 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# GIMP - The GNU Image Manipulation Program
|
|
# Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
#
|
|
# gimptestframework.py
|
|
# Copyright (C) 2021-2024 Jacob Boerema
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
"""GIMP file plug-ins testing framework."""
|
|
|
|
import os
|
|
import configparser
|
|
|
|
import xml
|
|
from xml.etree.ElementTree import ElementTree, Element
|
|
|
|
import gi
|
|
gi.require_version('Gimp', '3.0')
|
|
from gi.repository import Gimp
|
|
from gi.repository import Gio
|
|
|
|
|
|
VERSION = "0.5"
|
|
AUTHORS = "Jacob Boerema"
|
|
YEARS = "2021-2024"
|
|
|
|
EXPECTED_FAIL = 0
|
|
EXPECTED_OK = 1
|
|
EXPECTED_TODO = 2
|
|
|
|
RESULT_FAIL = 0
|
|
RESULT_OK = 1
|
|
RESULT_CRASH = 2
|
|
|
|
|
|
class PluginTestConfig(object):
|
|
def __init__(self, log, path, testconfig):
|
|
self.valid_config = False
|
|
self.enabled = False
|
|
|
|
self.plugin = testconfig.get('plugin-import')
|
|
if self.plugin is None:
|
|
self.plugin = testconfig.get('plugin')
|
|
if self.plugin is not None:
|
|
log.warning("Test is using deprecated 'plugin' parameter. Use 'plugin-import' instead!")
|
|
else:
|
|
log.error("Missing required 'plugin-import' parameter!")
|
|
return
|
|
|
|
tests = testconfig.get('tests')
|
|
if tests is None:
|
|
log.error("Missing required 'tests' parameter!")
|
|
return
|
|
testsuite_basename = tests + '-'
|
|
self.tests = path + tests
|
|
|
|
enabled = testconfig.get('enabled')
|
|
if enabled == 'True':
|
|
self.enabled = True
|
|
|
|
self.extension = testconfig.get('extension','<missing>')
|
|
self.valid_config = True
|
|
|
|
# preliminary export testing support
|
|
self.export_plugin = testconfig.get('plugin-export')
|
|
self.export_tests = testconfig.get('tests-export')
|
|
self.export_enabled = testconfig.get('enabled-export')
|
|
|
|
self.testsuite_import = testsuite_basename + self.plugin
|
|
if self.export_plugin is not None:
|
|
self.testsuite_export = testsuite_basename + self.export_plugin
|
|
|
|
|
|
class ConfigLoader(object):
|
|
def __init__(self, log, config_path, config_file):
|
|
self.config_path = config_path
|
|
self.config_file = config_file
|
|
log.debug(f"Using config file: {self.config_path}{self.config_file}")
|
|
self.tests = []
|
|
self.export_tests = []
|
|
self.problems = 0
|
|
self.disabled = 0
|
|
self.config = configparser.ConfigParser()
|
|
self.config.read(self.config_path + self.config_file)
|
|
|
|
msg = "Available tests:"
|
|
for key in self.config.sections():
|
|
if key == 'main':
|
|
pass
|
|
else:
|
|
# Add test config
|
|
test = PluginTestConfig(log, self.config_path, self.config[key])
|
|
if test.valid_config:
|
|
self.tests.append(test)
|
|
msg += f"\nPlugin: {test.plugin}, test config: {test.tests}, enabled: {test.enabled}"
|
|
if not test.enabled:
|
|
self.disabled += 1
|
|
else:
|
|
log.warning(f"\nSkipping invalid config for {key}!")
|
|
self.problems += 1
|
|
|
|
if test.export_plugin is not None and test.export_tests is not None:
|
|
log.info(f"Export test found for {key}")
|
|
self.export_tests.append(test)
|
|
|
|
log.gimp_verbose_message(msg)
|
|
|
|
class FileLoadTest(object):
|
|
def __init__(self, file_load_plugin_name, image_type, data_path, log, testsuite):
|
|
self.log = log
|
|
self.testsuite = testsuite
|
|
self.data_root = data_path
|
|
self.plugin_name = file_load_plugin_name
|
|
self.image_type = image_type
|
|
|
|
self.unexpected_success_images = []
|
|
self.unexpected_failure_images = []
|
|
|
|
self.failure_reason = "unknown"
|
|
|
|
self.total_tests = 0
|
|
self.total_failed = 0
|
|
self.total_todo = 0
|
|
self.total_ok = 0
|
|
self.total_crash = 0
|
|
|
|
def run_file_load(self, image_file, expected):
|
|
if not os.path.exists(image_file):
|
|
msg = "Regression loading " + image_file + ". File does not exist!"
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_FAIL
|
|
|
|
pdb_proc = Gimp.get_pdb().lookup_procedure(self.plugin_name)
|
|
if pdb_proc is None:
|
|
msg = "Plug-in procedure '" + self.plugin_name + "' not found!"
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_FAIL
|
|
pdb_config = pdb_proc.create_config()
|
|
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
|
|
pdb_config.set_property('file', Gio.File.new_for_path(image_file))
|
|
result = pdb_proc.run(pdb_config)
|
|
status = result.index(0)
|
|
img = result.index(1)
|
|
if (status == Gimp.PDBStatusType.SUCCESS and img is not None and img.is_valid()):
|
|
self.log.info ("Loading succeeded for " + image_file)
|
|
img.delete()
|
|
if expected == EXPECTED_FAIL:
|
|
self.unexpected_success_images.append(image_file)
|
|
msg = "Regression loading " + image_file + ". Loading unexpectedly succeeded."
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_FAIL
|
|
elif expected == EXPECTED_TODO:
|
|
self.unexpected_success_images.append(image_file)
|
|
msg = "Loading unexpectedly succeeded for test marked TODO. Image: " + image_file
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_FAIL
|
|
|
|
return RESULT_OK
|
|
else:
|
|
self.log.info ("Loading failed for " + image_file)
|
|
if status == Gimp.PDBStatusType.CALLING_ERROR:
|
|
# A calling error indicates the plug-in crashed, which should
|
|
# always be considered failure!
|
|
if expected == EXPECTED_OK:
|
|
self.unexpected_failure_images.append(image_file)
|
|
elif expected == EXPECTED_TODO:
|
|
self.unexpected_success_images.append(image_file)
|
|
msg = "Plug-in crashed while loading " + image_file + "."
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_CRASH
|
|
elif expected == EXPECTED_OK:
|
|
self.unexpected_failure_images.append(image_file)
|
|
msg = "Regression loading " + image_file + ". Loading unexpectedly failed."
|
|
self.failure_reason = msg
|
|
self.log.error("--> " + msg)
|
|
return RESULT_FAIL
|
|
return RESULT_OK
|
|
|
|
def load_test_images(self, test_images):
|
|
test_list = []
|
|
|
|
if not os.path.exists(test_images):
|
|
msg = "Path does not exist: " + test_images
|
|
self.log.error(msg)
|
|
return None
|
|
|
|
with open(test_images, encoding='UTF-8') as f:
|
|
try:
|
|
content = f.readlines()
|
|
except UnicodeDecodeError as err:
|
|
self.log.error(f"Invalid encoding for {test_images}: {err}")
|
|
return None
|
|
|
|
# strip whitespace
|
|
content = [x.strip() for x in content]
|
|
|
|
for line in content:
|
|
data = line.split(",")
|
|
data[0] = data[0].strip()
|
|
if len(data) > 1:
|
|
expected_str = data[1].strip()
|
|
else:
|
|
expected_str = 'EXPECTED_OK'
|
|
data.append(expected_str)
|
|
|
|
if expected_str == 'EXPECTED_FAIL':
|
|
data[1] = EXPECTED_FAIL
|
|
elif expected_str == 'EXPECTED_TODO':
|
|
data[1] = EXPECTED_TODO
|
|
elif expected_str == 'SKIP':
|
|
continue
|
|
else:
|
|
# Assume that anything else is supposed to load OK
|
|
data[1] = EXPECTED_OK
|
|
test_list.append(data)
|
|
|
|
return test_list
|
|
|
|
def run_tests(self, image_folder, test_images, test_description):
|
|
test_fail = 0
|
|
test_ok = 0
|
|
test_todo = 0
|
|
test_crash = 0
|
|
test_total = 0
|
|
test_images_list = self.load_test_images(test_images)
|
|
if not test_images_list:
|
|
# Maybe we should create another type of test failure (test_filesystem?)
|
|
test_total += 1
|
|
test_fail += 1
|
|
test_crash += 1
|
|
self.total_failed += test_fail
|
|
self.total_crash += test_crash
|
|
msg = f"No images found for '{test_description}'.'"
|
|
self.log.error(msg)
|
|
return
|
|
test_total = len(test_images_list)
|
|
|
|
for imgfile, expected in test_images_list:
|
|
test_result = self.run_file_load(self.data_root + image_folder + imgfile, expected)
|
|
el = Element("testcase")
|
|
el.set('classname', self.testsuite.get('classname', 'unknown'))
|
|
el.set('name', image_folder + imgfile)
|
|
el.set('file', imgfile)
|
|
self.testsuite.append(el)
|
|
if test_result == RESULT_OK:
|
|
test_ok += 1
|
|
else:
|
|
if test_result == RESULT_FAIL:
|
|
test_fail += 1
|
|
elif test_result == RESULT_CRASH:
|
|
test_crash += 1
|
|
test_fail += 1
|
|
else:
|
|
test_crash += 1
|
|
test_fail += 1
|
|
msg = "Invalid test result value: " + str(test_result)
|
|
self.log.error(msg)
|
|
|
|
# Add failed testcase
|
|
failure = Element("failure")
|
|
failure.set('type', 'failure')
|
|
failure.text = self.failure_reason
|
|
el.append(failure)
|
|
|
|
if expected == EXPECTED_TODO:
|
|
test_todo += 1
|
|
|
|
result_msg = "Test: " + test_description + "\n"
|
|
if test_crash > 0:
|
|
result_msg += "Number of plug-in crashes: " + str(test_crash) + "\n"
|
|
|
|
if test_fail > 0:
|
|
result_msg += "Failed " + str(test_fail) + " of " + str(test_total) + " tests"
|
|
if test_todo > 0:
|
|
result_msg += ", and " + str(test_todo) + " known test failures."
|
|
elif test_todo > 0:
|
|
result_msg += "All " + str(test_total) + " tests succeeded, but there are " + str(test_todo) + " known test failures."
|
|
else:
|
|
result_msg += "All " + str(test_total) + " tests succeeded."
|
|
|
|
self.log.info("\n--- results ---")
|
|
self.log.info(result_msg)
|
|
if ((test_fail > 0 or test_todo > 0) and self.log.interactive):
|
|
Gimp.message (result_msg)
|
|
|
|
self.total_tests += test_total
|
|
self.total_todo += test_todo
|
|
self.total_failed += test_fail
|
|
self.total_ok += test_ok
|
|
self.total_crash += test_crash
|
|
|
|
def show_results_total(self):
|
|
msg = "\n----- Test result totals -----"
|
|
msg += f"\nTotal {self.image_type} tests: {self.total_tests}"
|
|
if self.total_crash > 0:
|
|
msg += f"\nTests crashed: {self.total_crash}"
|
|
msg += f"\nTests failed: {self.total_failed}"
|
|
if self.total_crash > 0:
|
|
msg += f" (including crashes)"
|
|
msg += f"\nTests todo: {self.total_todo}"
|
|
self.log.message(msg)
|
|
|
|
msg = ""
|
|
for img in self.unexpected_success_images:
|
|
msg += f"{img}\n"
|
|
if len(msg) > 0:
|
|
msg = "\nImages that unexpectedly loaded:\n" + msg
|
|
self.log.message(msg)
|
|
msg = ""
|
|
for img in self.unexpected_failure_images:
|
|
msg += f"{img}\n"
|
|
if len(msg) > 0:
|
|
msg = "\nImages that unexpectedly failed to load:\n" + msg
|
|
self.log.message(msg)
|
|
|
|
class RunTests(object):
|
|
def __init__(self, test, config_path, data_path, log, testsuite):
|
|
self.test_count = 0
|
|
self.regression_count = 0
|
|
if os.path.exists(test.tests):
|
|
self.plugin_test = FileLoadTest(test.plugin, test.extension, data_path, log, testsuite)
|
|
cfg = configparser.ConfigParser()
|
|
cfg.read(test.tests)
|
|
for subtest in cfg.sections():
|
|
if not 'description' in cfg[subtest]:
|
|
description = "Missing description for " + test.extension
|
|
else:
|
|
description = cfg[subtest]['description']
|
|
enabled = True
|
|
if 'enabled' in cfg[subtest]:
|
|
if cfg[subtest]['enabled'] == 'False':
|
|
enabled = False
|
|
folder = cfg[subtest]['folder']
|
|
files = self.plugin_test.data_root + folder + cfg[subtest]['files']
|
|
if enabled:
|
|
self.plugin_test.run_tests(folder, files, description)
|
|
else:
|
|
log.info(f"Testing is disabled for: {description}.")
|
|
|
|
self.plugin_test.show_results_total()
|
|
self.test_count = self.plugin_test.total_tests
|
|
self.regression_count = self.plugin_test.total_failed
|
|
else:
|
|
self.plugin_test = None
|
|
log.error("Test path " + test.tests + " does not exist!")
|
|
self.regression_count = 1
|
|
|
|
def get_unexpected_success_regressions(self):
|
|
return self.plugin_test.unexpected_success_images
|
|
|
|
def get_unexpected_failure_regressions(self):
|
|
return self.plugin_test.unexpected_failure_images
|
|
|
|
def get_todo_count(self):
|
|
return self.plugin_test.total_todo
|
|
|
|
def get_crash_count(self):
|
|
return self.plugin_test.total_crash
|
|
|
|
|
|
class GimpTestRunner(object):
|
|
def __init__(self, log, test_type, test_cfg):
|
|
self.log = log
|
|
self.test_type = test_type
|
|
self.test_cfg = test_cfg
|
|
|
|
self.tests_total = 0
|
|
self.error_total = 0
|
|
self.todo_total = 0
|
|
self.crash_total = 0
|
|
|
|
self.unexpected_success_images = []
|
|
self.unexpected_failure_images = []
|
|
|
|
el = Element("testsuites")
|
|
el.set('name', test_type)
|
|
self.xml_tree = ElementTree(el)
|
|
|
|
def print_header(self, test_type):
|
|
divider = "\n--------------------------------"
|
|
msg = f"\nGIMP file plug-in tests version {VERSION}\n"
|
|
#msg += f"\n{AUTHORS}, {YEARS}\n"
|
|
msg += f"\n--- Starting {test_type} test run ---"
|
|
msg += divider
|
|
|
|
if self.log.verbose:
|
|
msg += f"\nConfig: {self.test_cfg.config_folder + self.test_cfg.config_file}"
|
|
msg += f"\nLog: {self.test_cfg.log_file}"
|
|
msg += f"\nData path: {self.test_cfg.data_folder}"
|
|
|
|
self.log.message(msg)
|
|
|
|
def print_footer(self, msg):
|
|
msg += "\n--------------------------------"
|
|
self.log.message(msg)
|
|
|
|
def list_regressions(self):
|
|
regression_list = ""
|
|
msg = ""
|
|
extra = "\n"
|
|
for img in self.unexpected_success_images:
|
|
msg += f"\n{img}"
|
|
if len(msg) > 0:
|
|
regression_list = "\nImages that unexpectedly loaded:" + msg + extra
|
|
extra = ""
|
|
|
|
msg = ""
|
|
for img in self.unexpected_failure_images:
|
|
msg += f"\n{img}"
|
|
if len(msg) > 0:
|
|
regression_list += "\nImages that unexpectedly failed to load:" + msg + extra
|
|
|
|
return regression_list
|
|
|
|
def run_tests(self):
|
|
self.print_header(self.test_type)
|
|
|
|
# Load test config
|
|
cfg = ConfigLoader(self.log, self.test_cfg.config_folder, self.test_cfg.config_file)
|
|
|
|
root = self.xml_tree.getroot()
|
|
|
|
# Actual tests
|
|
for test in cfg.tests:
|
|
if test.enabled:
|
|
self.log.consoleinfo(f"\nTesting {test.extension} import using {test.plugin}...\n")
|
|
el = Element("testsuite")
|
|
el.set('name', test.testsuite_import)
|
|
el.set('classname', test.testsuite_import)
|
|
plugin_tests = RunTests(test, cfg.config_path, self.test_cfg.data_folder, self.log, el)
|
|
self.tests_total += plugin_tests.test_count
|
|
self.error_total += plugin_tests.regression_count
|
|
|
|
el.set('tests', str(plugin_tests.test_count))
|
|
el.set('failures', str(plugin_tests.regression_count))
|
|
el.set('errors', '0')
|
|
el.set('skipped', '0')
|
|
root.append(el)
|
|
|
|
if plugin_tests.plugin_test is not None:
|
|
self.todo_total += plugin_tests.get_todo_count()
|
|
self.crash_total += plugin_tests.get_crash_count()
|
|
if plugin_tests.regression_count > 0:
|
|
temp = plugin_tests.get_unexpected_success_regressions()
|
|
if len(temp) > 0:
|
|
self.unexpected_success_images.extend(temp)
|
|
temp = plugin_tests.get_unexpected_failure_regressions()
|
|
if len(temp) > 0:
|
|
self.unexpected_failure_images.extend(temp)
|
|
else:
|
|
self.crash_total += 1
|
|
self.log.consoleinfo(f"\nFinished testing {test.extension}\n")
|
|
else:
|
|
self.log.consoleinfo(f"Testing {test.extension} import using {test.plugin} is disabled.")
|
|
|
|
root.set('tests', str(self.tests_total))
|
|
root.set('failures', str(self.error_total))
|
|
root.set('errors', '0')
|
|
|
|
xml.etree.ElementTree.indent(self.xml_tree)
|
|
self.xml_tree.write(self.test_cfg.xml_results_file, 'UTF-8')
|
|
|
|
msg = "\n--------- Test results ---------"
|
|
if cfg.disabled > 0:
|
|
msg += f"\nNumber of test sections disabled: {cfg.disabled}"
|
|
if cfg.problems > 0:
|
|
msg += f"\nNumber of test sections skipped due to configuration errors: {cfg.problems}"
|
|
msg += f"\nTotal number of tests executed: {self.tests_total}"
|
|
msg += f"\nTotal number of regressions: {self.error_total}"
|
|
if self.crash_total > 0:
|
|
msg += f"\nTotal number of crashes: {self.crash_total}"
|
|
if self.todo_total > 0:
|
|
msg += f"\nTotal number of todo tests: {self.todo_total}"
|
|
if self.error_total > 0:
|
|
msg += "\n" + self.list_regressions()
|
|
self.print_footer(msg)
|
|
|
|
class GimpExportTestGroup(object):
|
|
def __init__(self, group_test_cfg, group_name, group_description,
|
|
input_folder, output_folder):
|
|
self.group_test_cfg = group_test_cfg
|
|
self.group_name = group_name
|
|
self.group_description = group_description
|
|
self.input_folder = input_folder
|
|
self.output_folder = output_folder
|
|
|
|
class GimpExportTestSections(object):
|
|
def __init__(self, test, config_path, data_path, log):
|
|
self.test_groups = []
|
|
self.test_count = 0
|
|
self.regression_count = 0
|
|
self.export_test_path = config_path + test.export_tests
|
|
if os.path.exists(self.export_test_path):
|
|
cfg = configparser.ConfigParser()
|
|
cfg.read(self.export_test_path)
|
|
for subtest in cfg.sections():
|
|
if not 'description' in cfg[subtest]:
|
|
description = "Missing description for " + test.extension
|
|
log.warning(f"Missing description for {test.extension}")
|
|
else:
|
|
description = cfg[subtest]['description']
|
|
log.info(f"Test: {description}")
|
|
enabled = True
|
|
if 'enabled' in cfg[subtest]:
|
|
if cfg[subtest]['enabled'] == 'False':
|
|
enabled = False
|
|
|
|
folder_input = cfg[subtest]['folder-input']
|
|
folder_output = cfg[subtest]['folder-output']
|
|
if enabled:
|
|
group = GimpExportTestGroup(test, test.extension, description,
|
|
folder_input, folder_output)
|
|
self.test_groups.append(group)
|
|
else:
|
|
log.info(f"Testing is disabled for: {description}.")
|
|
|
|
else:
|
|
log.error("Test path " + test.export_tests + " does not exist!")
|
|
self.regression_count = 1
|
|
|
|
class GimpExportTest(object):
|
|
def __init__(self, group_name, log):
|
|
self.group_name = group_name
|
|
self.log = log
|
|
|
|
self.base_path = None
|
|
self.input_path = None
|
|
self.output_path = None
|
|
self.file_import = None
|
|
self.file_export = None
|
|
|
|
def _setup(self, group_config, test_data_path):
|
|
self.base_path = test_data_path
|
|
self.input_path = self.base_path + group_config.input_folder
|
|
self.output_path = self.base_path + group_config.output_folder
|
|
if self.base_path is None or self.input_path is None or self.output_path is None:
|
|
return False
|
|
|
|
self.file_import = group_config.group_test_cfg.plugin
|
|
self.file_export = group_config.group_test_cfg.export_plugin
|
|
if self.file_import is None or self.file_export is None:
|
|
return False
|
|
return True
|
|
|
|
def setup(self, group_config):
|
|
pass
|
|
|
|
def teardown(self, group_config):
|
|
pass
|
|
|
|
def run_test(self, group_config):
|
|
pass
|
|
|
|
def execute(self, group_config, test_data_path):
|
|
if not self._setup(group_config, test_data_path):
|
|
self.log.error("Invalid config, can't execute test!")
|
|
return
|
|
self.setup(group_config)
|
|
self.run_test(group_config)
|
|
self.teardown(group_config)
|
|
|
|
class GimpExportTestRunner(GimpTestRunner):
|
|
def __init__(self, log, test_type, test_cfg):
|
|
self.cfg = None
|
|
self.log = log
|
|
self.test_type = test_type
|
|
self.test_cfg = test_cfg
|
|
self.sections = None
|
|
self.tests = []
|
|
super().__init__(log, test_type, test_cfg)
|
|
|
|
def load_test_configs(self):
|
|
self.cfg = ConfigLoader(self.log, self.test_cfg.config_folder,
|
|
self.test_cfg.config_file)
|
|
for test in self.cfg.export_tests:
|
|
if test.export_enabled:
|
|
self.sections = GimpExportTestSections(test, self.cfg.config_path,
|
|
self.test_cfg.data_folder,
|
|
self.log)
|
|
else:
|
|
pass
|
|
|
|
def get_test_group_config(self, test_name):
|
|
#FIXME maybe have this sorted and use a search...
|
|
if self.sections is not None:
|
|
for test in self.sections.test_groups:
|
|
if test.group_name == test_name:
|
|
return test
|
|
return None
|
|
|
|
def add_test(self, test_class):
|
|
self.log.info(f"Adding tests for {test_class.group_name}")
|
|
self.tests.append(test_class)
|
|
|
|
def run_tests(self):
|
|
self.print_header(self.test_type)
|
|
for test in self.tests:
|
|
self.log.message(f"Running tests for {test.group_name}")
|
|
test_config = self.get_test_group_config(test.group_name)
|
|
if test_config is None:
|
|
self.log.error(f"Could not find configuration for {test.group_name} tests!")
|
|
else:
|
|
test.execute(test_config, self.test_cfg.data_folder)
|
|
self.print_footer("Export tests finished")
|
|
|
|
def show_results(self):
|
|
#FIXME export results are not implemented yet
|
|
pass
|