Esempio n. 1
0
def build_context_from_config(config: Config) -> BuildContext:
    """Creates a new BuildContext instance from the provided *config*.

    This function is mainly for the purpose of a manual build trigger. For manually triggering a build there
    obviously is no request data from which to get the repo url / commit hash from, which is why it will use the
    information which were defined in the config file.

    :return: The build context which was based on the information in the config file in the ufotest folder
    """
    repository_url = config.get_ci_repository_url()
    repository_branch = config.get_ci_branch()
    repository_commit = 'FETCH_HEAD'
    test_suite = config.get_ci_suite()

    return BuildContext(repository_url, repository_branch, repository_commit,
                        test_suite)
Esempio n. 2
0
    def __init__(self, test_runner: TestRunner,
                 tests: List[Type[AbstractTest]], name: str):
        self.test_runner = test_runner
        self.tests = tests
        self.suite_name = name
        self.config = Config()

        self.results = {}
Esempio n. 3
0
def build_context_from_request(data: dict) -> BuildContext:
    """Creates a new BuildContext instance from given json payload *data* from the github web hook request.

    This function extracts the information about which commit to clone from which repo from the given payload of the
    request.

    :return: the build context instance.
    """
    repository_url = data['repository']['clone_url']
    repository_branch = data['ref'].replace('refs/heads/', '')
    repository_commit = data['commits'][-1]

    config = Config()
    test_suite = config.get_ci_suite()

    return BuildContext(repository_url, repository_branch, repository_commit,
                        test_suite)
Esempio n. 4
0
    def setUpClass(cls) -> None:

        # So the main problem with testing the ufotest application is that the test cases are obviously not meant to
        # modify the already existing installation on the PC, which is why a new installation has to be created for
        # just for testing. The "tempfile" module is perfect for this.
        cls.temp_folder = tempfile.TemporaryDirectory()
        cls.folder_path = os.path.join(cls.temp_folder.name, '.ufotest')

        # The installation path for the project can be controlled by using the content
        os.environ['UFOTEST_PATH'] = cls.folder_path
        init_install()

        # After the installation we also need to reload the config dictionary for the whole system
        cls.config = Config()
        cls.config.reload()

        # This will method is needed to initialize the plugin manager and the script manager!
        cls.config.prepare()
Esempio n. 5
0
    def __init__(self,
                 repository_url: str,
                 branch_name: str,
                 commit_name: str,
                 test_suite: str,
                 config: Config = Config()):
        # constructed attributes
        self.repository_url = repository_url
        self.branch = branch_name
        self.commit = commit_name
        self.test_suite = test_suite

        # calculated attributes
        self.config = config
        self.creation_datetime = datetime.datetime.now()
        self.version = get_version()
        self.test_context = TestContext()

        # derived attributes
        self.repository_name = get_repository_name(repository_url)
        self.repository_path = get_path(self.repository_name)
        # Adding a portion of the commit hash to the folder name has been a recent addition, because I noticed that the
        # program crashes if there are two build triggered in the same minute. Also: Adding the commit also has the
        # advantage of additional information.
        self.folder_name = '{}__{}__{}'.format(
            self.repository_name, self.commit[:6],
            self.creation_datetime.strftime('%Y_%m_%d__%H_%M_%S'))
        self.folder_path = get_path('builds', self.folder_name)

        # non initialized attributes
        # These are the attributes, which have to be declared, but which cannot be initialized in the constructor.
        # The start and end time of course can only be acquired during the actual start and end.
        # The bitfile path actually has to be set by the BuildRunner. It is not known in before hand! The test report
        # as well can obviously only be added after the actual test are done.
        self.start_datetime: Optional[datetime.datetime] = None
        self.end_datetime: Optional[datetime.datetime] = None
        self.bitfile_path = None
        self.test_report: Optional[TestReport] = None

        # This is a flag, which is supposed to be set by the build runner, after the build process is over. This would
        # signify that the build context indeed contains information about a valid build process.
        self.completed = False
Esempio n. 6
0
    def __init__(self,
                 exit_code: int,
                 file_path: str,
                 description: str,
                 url_base: str = '/static'):
        AbstractTestResult.__init__(self, exit_code)

        self.file_path = file_path
        self.url_base = url_base
        self.description = description
        self.config = Config()

        # The workings of this class has been changed: Previously the url of the file was manually assembled using
        # os.path.join, now though config.url() is used and that method does not like the slashes being part of the
        # string, which is why we strip them here.
        self.url_base_clean = self.url_base.lstrip('/').rstrip('/')

        # The file name is important here because ultimately this object also has to be converted into an HTML file and
        # within an html file the file system path is not so important. More important is the URL path. So to make this
        # work at all.
        self.file_name = os.path.basename(self.file_path)
Esempio n. 7
0
    def __init__(self, config: Config = Config()):
        self.config = config
        self.modules = {}
        self.tests = {}

        self.archive_folder_path = self.config.get_archive_path()
        # So here I have added a separate step/attribute, which adds the pure folder name string to be saved separately
        # instead of directly assembling it into the absolute path. This value will be needed for example to
        # construct the url to the file server which can be used to view the reports online.
        self.start_time = datetime.datetime.now()
        self.name = self.start_time.strftime('test_run_%d_%m_%Y__%H_%M_%S')
        self.folder_path = os.path.join(self.archive_folder_path, self.name)

        create_folder(self.folder_path)

        # Initializing the logging
        self.logger = logging.Logger('TestRunner')

        # "test_folders" was originally a class attribute and the static and dynamic folder paths where global
        # variables for this folder, but then during testing I realized that CONFIG might not be loaded at that point
        # thus producing a key error for "tests"...
        static_test_folder = os.path.join(PATH, 'tests')
        dynamic_test_folder = self.config['tests']['folder']
        self.test_folders = [static_test_folder, dynamic_test_folder]
Esempio n. 8
0
    def __init__(self, config: Config = Config()):
        self.config = config

        self.date_time = datetime.datetime.now()
        self.platform = platform.platform()
        self.version = get_version()
Esempio n. 9
0
    def __init__(self, *additional_test_folders, config=Config()):
        # constructed attributes
        self.additional_test_folders = additional_test_folders

        # calculated attributes
        self.config = config
        self.archive_folder_path = self.config.get_archive_path()
        self.creation_datetime = datetime.datetime.now()
        self.folder_name = self.creation_datetime.strftime(
            self.ARCHIVE_FOLDER_FORMAT)
        self.folder_path = os.path.join(self.archive_folder_path,
                                        self.folder_name)
        # I have added this, because I realized, that the test result objects have to access this value for
        # correctly creating html links to additional resources saved within the test's folder.
        self.relative_url = os.path.join('archive', self.folder_name)
        self.folder_url = self.config.url(self.relative_url)
        # This general information, about the platform and version on which the test was executed, was previously
        # encapsulated by the TestMetadata class. But now all of this information os combined into the context. This
        # is because the responsibility of the context essentially is to provide such "contextual" information
        self.platform = platform.platform()
        self.version = get_version()  # the version of this software

        # So there are multiple methods for loading tests and also places from where tests can be loaded. Honestly at
        # this point I even think there are too many methods! So the first method is the the "static" tests folder.
        # These are the test which come pre-installed with the ufotest package and which are always present. The
        # "dynamic" method refers to the "test" folder in the installation folder of ufotest. Putting any python module
        # in there will make them be interpreted as test modules. This is probably the way to go for very local ad hoc
        # tests. Then there is the possibility to add folders when creating the TestContext object.
        # And most recently this list of test folders (every python module in such a folder will be interpreted as a
        # test module) can be modified with a filter hook. This is probably the prime way to add plugin specific tests
        # as it allows to tie in plugin specific test folders easily!
        static_test_folder = os.path.join(PATH, 'tests')
        dynamic_test_folder = self.config.get_test_folder()
        self.test_folders = [
            static_test_folder, dynamic_test_folder,
            *self.additional_test_folders
        ]
        self.test_folders = self.config.pm.apply_filter(
            'test_folders', self.test_folders)

        # -- The logging is supposed to be saved into a file within the correct archive folder for this test execution.
        self.logger = logging.Logger('TestContext')
        self.logger.setLevel(logging.DEBUG)
        self.logger_path = os.path.join(self.folder_path, 'context.log')

        # Non initialized attributes
        # So the way this context object works is that a new context object is being created for every testing process
        # the instance of this class will ultimately be passed to the TestRunner object which actually executes the
        # the test process. The responsibility of the context is to provide all the information which is required to
        # specify an individual process. But the responsibility is more than that. The context will also save the
        # results of the test runner. It does that to then later be passed to a TestReport. So for this purpose it has
        # to have some empty properties which are only later being set by the TestRunner.
        self.start_datetime: Optional[datetime.datetime] = None
        self.end_datetime: Optional[datetime.datetime] = None
        self.results: Dict[str, AbstractTestResult] = {}
        self.tests = {}
        self.name: Optional[str] = None

        self.hardware_version = None
        self.firmware_version = None
        self.sensor_version = None

        self.config.pm.do_action('post_test_context_construction',
                                 context=self,
                                 namespace=globals())
Esempio n. 10
0
import shutil

import click
from flask import Flask, request, send_from_directory, jsonify

from ufotest.config import Config, get_path
from ufotest.util import get_template, get_version
from ufotest.util import cerror, cprint, cresult
from ufotest.util import get_build_reports, get_test_reports
from ufotest.util import get_folder_size, format_byte_size
from ufotest.exceptions import BuildError
from ufotest.camera import UfoCamera
from ufotest.ci.build import BuildQueue, BuildLock, BuildRunner, BuildReport, build_context_from_request
from ufotest.ci.mail import send_report_mail

CONFIG = Config()
PATH = get_path()

ARCHIVE_PATH = os.path.join(PATH, 'archive')
BUILDS_PATH = os.path.join(PATH, 'builds')
STATIC_PATH = os.path.join(PATH, 'static')
PLUGINS_PATH = os.path.join(PATH, 'plugins')


class BuildAdapterGitlab(object):
    def __init__(self, data: dict):
        self.data = data

    def get(self):
        build = {
            'repository': {
Esempio n. 11
0
 def __init__(self, hook_name: str, priority: int, config=Config()):
     self.hook_name = hook_name
     self.priority = priority
     self.config = config
Esempio n. 12
0
def cli(ctx, version, verbose, conf, mock):
    """
    UfoTest command line interface

    The main way to use UfoTest's functionality is by invoking the appropriate command. Each command has it's own set
    of arguments and options. To list these options and to show an explanation of the commands purpose, use the
    --help option which is available for every command:

    ufotest <subcommand> --help
    """
    # If the version option of the base command is invoked, this means that we simply want to print the version of the
    # project and then terminate execution
    if version:
        # "get_version" reads the version of the software from the corresponding file in the source folder.
        version = get_version()
        click.secho(version, bold=True)
        sys.exit(0)

    # We acquire an instance of the config object here in the base command and then simply pass it as a context to each
    # respective sub command. Sure, the Config class is a singleton anyways and we could also invoke it in every
    # sub command, but like this we can do something cool. We simply add a --verbose option to this base command and
    # then save the value in the config, since we pass it along this also affects all sub commands and we dont need
    # to implement the --verbose option for every individual sub command.
    config = Config()
    config['context']['verbose'] = verbose
    ctx.obj = config

    # This fixes an important bug: Previously when any command was executed, the program attempted to call the prepare
    # method which would then in turn load the plugins. But that caused a problem when initially attempting to install
    # ufotest. For an initial installation there is not config file yet, but "prepare" and the init of the script and
    # plugin manager need certain config values! Thus ufotest could never be installed. Now we will treat the
    # installation command "init" as a special case which does not need the use the plugin system.
    if ctx.invoked_subcommand != 'init':
        # "prepare" sets up the correct jinja environment and initializes the plugin manager and the
        # script manager
        config.prepare()

        # Usually I wouldnt want to have to add the custom filters here but, sadly I have to due to pythons import
        # system. I would have liked to do it in "prepare" but in that module i cannot import anything from util (where
        # the actual method comes from) since util imports from that module...
        config.template_environment.filters[
            'format_byte_size'] = format_byte_size

        # This hook can be used to execute generic functionality before any command specific code is executed
        config.pm.do_action('pre_command',
                            config=config,
                            namespace=globals(),
                            context=ctx)

    for overwrite_string in conf:
        try:
            config.apply_overwrite(overwrite_string)
        except Exception:
            if config.verbose():
                cerror((
                    f'Could not apply config overwrite for value {overwrite_string}. Please check if the string is '
                    f'formatted correctly. The correct formatting would be "key.subkey=value". Also check if the '
                    f'variable even exists in the config file.'))

    if mock:
        # The "--mock" option for the main ufotest command implies that the mock camera class is to be used for the
        # execution of all sub commands. The first thing we need to do for this is to set the context flag "mock" to
        # True. This flag can be used by sub commands to check if the mock option has been invoked.
        # We could also check for the class of the camera, but that would be unreliable because technically a plugin
        # could change the mock camera class!
        config['context']['mock'] = True
        # This is what I meant: The exact camera class used as the mock is also subject to change by filter hook. On
        # default it will be MockCamera, but a plugin could decide to extend this class by subclassing or replace it
        # with something completely different
        mock_camera_class = config.pm.apply_filter('mock_camera_class',
                                                   MockCamera)
        # Then of course we modify the main "camera_class" filter to actually inject this mock camera class to be used
        # in all following scenarios.
        config.pm.register_filter('camera_class',
                                  lambda value: mock_camera_class, 1)
Esempio n. 13
0
 def __init__(self, name=None, commands=None, **attrs):
     click.Group.__init__(self, name, commands, **attrs)
     self.config = Config()