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 __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
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