def launch_django_application(self, test_case: TestCase):
        if not isinstance(test_case.attach, DjangoSettings):
            raise UnexpectedError(
                f'Django tests should have DjangoSettings class as an attach, '
                f'found {type(test_case.attach)}')

        source = test_case.source_name

        if source is None or not len(source):
            source = 'manage'

        full_source = source.replace('.', os.sep) + '.py'
        full_path = os.path.abspath(full_source)

        if not os.path.exists(full_path):
            filename = os.path.basename(full_source)
            folder, file = runnable_searcher(
                file_filter=lambda _, f: f == filename)
            full_path = os.path.abspath(folder + os.sep + file)

        self.full_path = full_path
        self.port = self.__find_free_port(test_case.attach.tryout_ports)

        if test_case.attach.use_database:
            self.__prepare_database(test_case.attach.test_database)

        self.process = PopenWrapper(sys.executable, self.full_path,
                                    'runserver', self.port, '--noreload')

        i: int = 100
        search_phrase = 'Starting development server at'
        while i:
            if search_phrase in self.process.stdout:
                test_case.attach.port = self.port
                break
            i -= 1

            if self.process.is_error_happened():
                i = 0
            else:
                sleep(0.1)
        else:
            stdout = self.process.stdout.strip()
            stderr = self.process.stderr.strip()

            error_info = f'Cannot start Django server because cannot find "{search_phrase}" in process\' output'
            if len(stdout):
                error_info += '\n\nstdout:\n' + stdout
            if len(stderr):
                error_info += '\n\nstderr:\n' + stderr

            raise ErrorWithFeedback(error_info)
예제 #2
0
    def __prepare_database(self, test_database: str):
        os.environ['HYPERSKILL_TEST_DATABASE'] = test_database
        with open(test_database, 'w'):
            pass
        migrate = PopenWrapper(sys.executable, self.full_path, 'migrate')

        while migrate.alive and len(migrate.stderr) == 0:
            sleep(0.01)

        if len(migrate.stderr) != 0:
            migrate.wait_stderr()

            stdout = migrate.stdout
            stderr = migrate.stderr

            error_info = 'Cannot apply migrations to an empty database.'
            if len(stdout):
                error_info += '\n\nstdout:\n' + stdout.strip()
            if len(stderr):
                error_info += '\n\nstderr:\n' + stderr.strip()

            raise ErrorWithFeedback(error_info)
    def launch_flask_applications(self, test_case: TestCase):
        if not isinstance(test_case.attach, FlaskSettings):
            raise UnexpectedError(
                f'Flask tests should have FlaskSettings class as an attach, '
                f'found {type(test_case.attach)}')

        sources = test_case.attach.sources

        if len(sources) == 0:
            raise UnexpectedError(
                f'Cannot find Flask applications to run, no sources were defined in tests'
            )

        new_sources = []

        for source in sources:
            filename, port = source

            full_source = filename.replace('.', os.sep) + '.py'
            full_path = os.path.abspath(full_source)

            if not os.path.exists(full_path):
                raise ErrorWithFeedback(
                    f'Cannot find file named "{os.path.basename(full_path)}" '
                    f'in folder "{os.path.dirname(full_path)}". '
                    f'Check if you deleted it.')

            if port is None:
                port = self.__find_free_port(test_case.attach.tryout_ports)

            process = PopenWrapper(sys.executable, full_path,
                                   f'localhost:{port}')
            self.processes += [(full_source, process)]

            i: int = 100
            search_phrase = '(Press CTRL+C to quit)'
            while i:
                if search_phrase in process.stderr:
                    break
                i -= 1

                if process.is_error_happened():
                    i = 0
                else:
                    sleep(0.1)
            else:
                stdout = process.stdout.strip()
                stderr = process.stderr.strip()

                error_info = (
                    f'Cannot start Flask server {full_source} '
                    f'because cannot find "{search_phrase}" in process\' output'
                )

                if len(stdout):
                    error_info += '\n\nstdout:\n' + stdout
                if len(stderr):
                    error_info += '\n\nstderr:\n' + stderr

                raise ErrorWithFeedback(error_info)

            new_sources += [(filename, port)]

        test_case.attach.sources = new_sources
예제 #4
0
class DjangoApplicationRunner(TestRunner):
    process: PopenWrapper = None
    port: Optional[int] = None
    full_path: Optional[str] = None

    def launch_django_application(self, test_case: TestCase):
        if not isinstance(test_case.attach, DjangoSettings):
            raise UnexpectedError(
                f'Django tests should have DjangoSettings class as an attach, '
                f'found {type(test_case.attach)}')

        source = test_case.source_name

        if source is None or not len(source):
            source = 'manage'

        full_source = source.replace('.', os.sep) + '.py'
        full_path = os.path.abspath(full_source)

        if not os.path.exists(full_path):
            filename = os.path.basename(full_source)
            folder, file = runnable_searcher(file_filter=lambda _, f: f == filename)
            full_path = os.path.abspath(folder + os.sep + file)

        self.full_path = full_path
        self.port = self.__find_free_port(test_case.attach.tryout_ports)

        if test_case.attach.use_database:
            self.__prepare_database(test_case.attach.test_database)

        self.process = PopenWrapper(
            sys.executable, self.full_path, 'runserver', self.port, '--noreload')

        i: int = 100
        search_phrase = 'Starting development server at'
        while i:
            if search_phrase in self.process.stdout:
                test_case.attach.port = self.port
                break
            i -= 1

            if self.process.is_error_happened():
                i = 0
            else:
                sleep(0.1)
        else:
            stdout = self.process.stdout.strip()
            stderr = self.process.stderr.strip()

            error_info = f'Cannot start Django server because cannot find "{search_phrase}" in process\' output'
            if len(stdout):
                error_info += '\n\nstdout:\n' + stdout
            if len(stderr):
                error_info += '\n\nstderr:\n' + stderr

            raise ErrorWithFeedback(error_info)

    def __find_free_port(self, ports: List[int]) -> int:
        for port in ports:
            if not is_port_in_use(port):
                return port
        raise ErrorWithFeedback(
            'Cannot find a port to start Django application '
            f'(tried ports form {ports[0]} to {ports[-1]})')

    def __prepare_database(self, test_database: str):
        os.environ['HYPERSKILL_TEST_DATABASE'] = test_database
        with open(test_database, 'w'):
            pass
        migrate = subprocess.Popen(
            [sys.executable, self.full_path, 'migrate'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        exit_code = migrate.wait()
        if exit_code != 0:
            stdout = migrate.stdout.read().decode().strip()
            stderr = migrate.stderr.read().decode().strip()

            error_info = 'Cannot apply migrations to an empty database.'
            if len(stdout):
                error_info += '\n\nstdout:\n' + stdout.strip()
            if len(stderr):
                error_info += '\n\nstderr:\n' + stderr.strip()

            raise ErrorWithFeedback(error_info)

    def set_up(self, test_case: TestCase):
        self.launch_django_application(test_case)

    def tear_down(self, test_case: TestCase):
        if isinstance(test_case.attach, DjangoSettings):
            safe_delete(test_case.attach.test_database)
        if self.process:
            self.process.terminate()

    def _check_errors(self):
        if self.process.is_error_happened():
            self.process.terminate()
            raise ErrorWithFeedback(self.process.stderr)

    def test(self, test_run: TestRun) -> Optional[CheckResult]:
        self._check_errors()

        test_case = test_run.test_case

        try:
            result = test_case.dynamic_testing()
            self._check_errors()
            return result
        except BaseException as ex:
            test_run.set_error_in_test(ex)

        error = test_run.error_in_test

        if isinstance(error, TestPassed):
            return CheckResult.correct()
        elif isinstance(error, WrongAnswer):
            return CheckResult.wrong(error.feedback)
        else:
            return None