Esempio n. 1
0
def create_success_output(binaries, tests_result):
    logger.debug('Creating json with tests results')

    teachers = {
        'compilation': [{
            'binary':
            binary,
            'configurations': [{
                'name': name,
                'result': result
            } for name, result in configurations.items()]
        } for binary, configurations in binaries.items()],
        'tests': [{
            'configuration':
            configuration,
            'cases': [{
                'name': name,
                'result': result
            } for name, result in cases.items()]
        } for configuration, cases in tests_result.items()]
    }

    with open(Config.teachers_json(), 'w') as f:
        json.dump(teachers, f, cls=EnhancedJSONEncoder, indent=4)

    students = {
        'compilation': [{
            'binary':
            binary,
            'configurations': [{
                'name':
                name,
                'result':
                result.compiler_output if Config.get_visibility(
                    name, Visibility.BUILD) else ''
            } for name, result in configurations.items()]
        } for binary, configurations in binaries.items()],
        'tests': [{
            'configuration':
            configuration,
            'cases': [{
                'name':
                name,
                'status':
                result.get_status() == tester.tests.TestResultStatus.SUCCESS,
                'result':
                result.stdout if Config.get_visibility(
                    configuration, Visibility.TESTS) else ''
            } for name, result in cases.items()]
        } for configuration, cases in tests_result.items()]
    }

    with open(Config.students_json(), 'w') as f:
        json.dump(students, f, cls=EnhancedJSONEncoder, indent=4)
Esempio n. 2
0
def build_tests():
    # copy submission to main test file
    if Config.get_mode() == SubmissionMode.COPY:
        # this is quite a hack, input file is always named main.cpp, so
        # we need to change that to header file
        shutil.copy2(os.path.join(Config.submission_path(), 'main.cpp'),
                     os.path.join(Config.tests_path(), 'submission.h'))

    result = {}
    for configuration in ['debug', 'release']:
        result[configuration] = build(
            os.path.join(Config.tests_path(), 'build-' + configuration))

    return result
Esempio n. 3
0
def configure():
    logging.config.dictConfig({
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'verbose': {
                'format':
                '{levelname:8s} {asctime} {module:10s} {funcName:20s} {message}',
                'style': '{',
            }
        },
        'handlers': {
            'file': {
                'level': 'DEBUG',
                'class': 'logging.FileHandler',
                'filename': os.path.join(Config.output_path(),
                                         'main-tester.log'),
                'mode': 'w',
                'formatter': 'verbose',
            },
            'console': {
                'level': 'INFO',
                'class': 'logging.StreamHandler',
                'formatter': 'verbose',
            },
        },
        'root': {
            'level': 'NOTSET',
            'handlers': ['console', 'file']
        },
    })
Esempio n. 4
0
def build(project_path):
    build_result = compiler.compile_cmake_project(project_path)

    build_output = os.path.join(Config.build_output_path(),
                                project_path.replace('/', '_') + '.txt')

    with open(build_output, "w") as text_file:
        text_file.write(build_result.compiler_output)

    return build_result
Esempio n. 5
0
def main():
    """
    Main entry point of our python tester. It will first of all
    load settings, compile sources, run tests and collect results,
    then it will pack those and send everything to output folder.
    """
    # start logger
    tester.logger.configure()
    logger.info('Tester started...')
    logger.debug(Config.dumps())

    compiler.check_cmake()

    test_results = {}

    binaries = {'tests': build_tests()}

    if Config.get_mode() == SubmissionMode.BUILD:
        binaries['submission'] = build_submission()

    for configuration, binary in binaries['tests'].items():
        if binary.errno != 0:
            continue  # build failed

        test_results[configuration] = {}
        tests = tester.tests.Tests(binary.output_path, configuration)
        for test_case in tests.test_cases:
            submission_binary = binaries.get('submission',
                                             {}).get(configuration, None)
            if submission_binary:
                if submission_binary.errno != 0:
                    continue  # cannot compile submission

                test_result = tests.run_test(test_case,
                                             submission_binary.output_path)
            else:
                test_result = tests.run_test(test_case)
            test_results[configuration][test_case] = test_result

    create_success_output(binaries, test_results)

    logger.info('Finished.')
Esempio n. 6
0
def handle_exception(exc_type, exc_value, exc_traceback):
    try:
        logger.exception('Uncaught exception',
                         exc_info=(exc_type, exc_value, exc_traceback))
        logger.debug('Creating json with errors')

        output = {
            'status':
            'Exception',
            'text':
            'This should never happen, please report this incident to your friendly administrators.',
        }

        with open(Config.teachers_json(), 'w') as f:
            json.dump(output, f)

        with open(Config.students_json(), 'w') as f:
            json.dump(output, f)
    finally:
        sys.exit(0)
Esempio n. 7
0
def build_submission():
    if Config.get_mode() != SubmissionMode.BUILD:
        return {}

    shutil.copy2(os.path.join(Config.submission_path(), 'main.cpp'),
                 Config.submission_project())

    result = {}
    for configuration in ['debug', 'release']:
        project_result = compiler.compile_cmake_lists(
            Config.submission_project(), configuration)

        build_output = os.path.join(
            Config.build_output_path(),
            Config.submission_project().replace('/', '_') + '-' +
            configuration + '-cmake-lists.txt')

        with open(build_output, "w") as text_file:
            text_file.write(project_result.compiler_output)

        if project_result.errno != 0:
            return {}

        result[configuration] = build(project_result.output_path)

    return result
Esempio n. 8
0
    def run_test(self, test_case, submission_binary=None):
        logger.info('Running test "%s" in configuration "%s".', test_case,
                    self.configuration)

        temp_dir = tempfile.mkdtemp()
        logger.debug('Using folder "%s"', temp_dir)

        catch_path = os.path.join(temp_dir, self.CATCH_EXEC_NAME)
        shutil.copy2(self.binary, catch_path)

        os.chmod(temp_dir, 0o777)  # everyone os allowed to do everything
        os.chmod(catch_path, 0o777)

        pw_record = pwd.getpwnam("apc-test")
        user_uid = pw_record.pw_uid
        user_gid = pw_record.pw_gid

        env = {}
        if submission_binary:
            submission_path = os.path.join(temp_dir, self.SUBMISSION_EXEC_NAME)
            shutil.copy2(submission_binary, submission_path)
            env = {
                'SUBMISSIONPATH': submission_path,
                'DATAPATH': Config.data_path(),
            }

        args = [*self._options, test_case.replace(',', '\,')
                ]  # comma in test is not allowed, you need to escape it

        logger.debug(
            'Starting tests file %s, with arguments "%s" current working directory "%s"',
            catch_path, ', '.join(args), temp_dir)

        def demote(user_uid, user_gid):
            os.setgid(user_gid)
            os.setuid(user_uid)

        with TimeoutManager() as timeout:
            try:
                catch = subprocess.run(
                    [catch_path, *args],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    preexec_fn=lambda: demote(user_uid, user_gid),
                    timeout=timeout,
                    cwd=temp_dir,
                    env=env)

                stdout = catch.stdout.decode('utf-8')
                stderr = catch.stderr.decode('utf-8')

                logger.info('Test finished errno: %d', catch.returncode)
                logger.debug('Test stdout: "%s"\n stderr: "%s"', stdout,
                             stderr)

                return TestResult(catch.returncode, stdout, stderr)
            except subprocess.TimeoutExpired:
                logger.info('Test timeouted.')
                # first negative number that cannot be represented with 32 bit signed int (assuming 2-complement)
                return TestResult(-2147483649, '',
                                  'Subprocess timeout expired!')
Esempio n. 9
0
 def __init__(self, binary, configuration):
     self.binary = binary
     self.configuration = configuration
     self._options = Config.get_catch2_configuration(configuration)
     self.test_cases = self._list_tests()