def runnable_searcher(abs_path_to_search: str = None, file_filter: Callable[[str, str], bool] = lambda folder, file: True) \ -> Tuple[str, str]: if abs_path_to_search is None: abs_path_to_search = os.getcwd() curr_folder = os.path.abspath(abs_path_to_search) for folder, dirs, files in walk_user_files(curr_folder): files = [f for f in files if f.endswith('.go') and file_filter(folder, f)] if len(files) == 0: continue if len(files) == 1: return folder, files[0] contents = {} for file in files: path = os.path.abspath(os.path.join(folder, file)) if path in file_contents_cached: contents[file] = file_contents_cached[path] elif os.path.exists(path): with open(path) as f: file_content = f.read() contents[file] = file_content file_contents_cached[path] = contents[file] has_main = {f: False for f in files} for file in files: source = contents[file] if GoRunnableFile.main_searcher.search(source) is not None: has_main[file] = True candidates = [f for f in files if has_main[f]] if len(candidates) == 1: return folder, candidates[0] if len(candidates) > 1: str_files = ', '.join(f'"{f}"' for f in candidates) raise ErrorWithFeedback( f'Cannot decide which file to run out of the following: {str_files}\n' 'They all have "func main()". Leave one file with main function.') raise ErrorWithFeedback( 'Cannot find a file with main function.\n' f'Are your project files located at \"{curr_folder}\"?')
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: source = 'manage' full_source = source.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.') 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: 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 tear_down(): if current_thread() != SystemHandler.__locker_thread: raise ErrorWithFeedback( "Cannot tear down the testing process from the other thread") with SystemHandler.__lock: if not SystemHandler.__locked: raise ErrorWithFeedback( "Cannot tear down the testing process more than once") SystemHandler.__locked = False SystemHandler.__locker_thread = None OutputHandler.revert_stdout() InputHandler.revert_input() ExitHandler.revert_exit()
def execute(self, stdin: str) -> str: if self.is_finished(): from hstest.stage_test import StageTest StageTest.curr_test_run.set_error_in_test( ErrorWithFeedback( f"The program {self} has unexpectedly terminated.\n" + "It finished execution too early, should continue running." )) raise TestedProgramFinishedEarly() if stdin is None: self.stop_input() return "" if not self.is_waiting_input(): raise UnexpectedError( f"Program {self} is not waiting for the input " + f"(state == \"{self._machine.state}\")") if self.__no_more_input: raise UnexpectedError( f"Can't pass input to the program {self} - input was prohibited." ) self._input = stdin if self.__in_background: self._machine.set_state(ProgramState.RUNNING) return "" # suspends thread while the program is executing, # waits for non-RUNNING state to be reached self._machine.set_and_wait(ProgramState.RUNNING) return self.__get_execution_output()
def __prepare_database(self, test_database: str): os.environ['HYPERSKILL_TEST_DATABASE'] = test_database with open(test_database, 'w'): pass migrate = ProcessWrapper(sys.executable, self.full_path, 'migrate', check_early_finish=True) while not migrate.is_finished() and len(migrate.stderr) == 0: sleep(0.01) if len(migrate.stderr) != 0: migrate.wait_output() 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 __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 set_up(): with SystemHandler.__lock: if SystemHandler.__locked: raise ErrorWithFeedback( "Cannot start the testing process more than once") SystemHandler.__locked = True SystemHandler.__locker_thread = current_thread() OutputHandler.replace_stdout() InputHandler.replace_input() ExitHandler.replace_exit()
def readline(self) -> str: line = self.handler.eject_next_line() if line is None: if not Settings.allow_out_of_input: from hstest import StageTest StageTest.curr_test_run.set_error_in_test(ErrorWithFeedback( "Program ran out of input. You tried to read more, than expected.")) raise ExitException() else: raise EOFError('EOF when reading a line') return line
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 check(self, reply: str, attach: Any): raise ErrorWithFeedback( f"The program has unexpectedly terminated.\n" + "It finished execution too early, should continue running.")
def runnable_searcher( abs_path_to_search: str, file_filter: Callable[[str, str], bool] = lambda x, y: True ) -> Tuple[str, str]: curr_folder = os.path.abspath(abs_path_to_search) test_folder = os.path.join(curr_folder, 'test') for folder, dirs, files in os.walk(curr_folder): if folder.startswith(test_folder): continue if folder == curr_folder: for file in 'test.py', 'tests.py': if file in files: files.remove(file) files = [ f for f in files if f.endswith('.py') and file_filter(folder, f) ] if len(files) == 0: continue if len(files) == 1: return folder, files[0] contents = {} for file in files: path = os.path.abspath(os.path.join(folder, file)) if path in _contents_cached: contents[file] = _contents_cached[path] elif os.path.exists(path): with open(path) as f: file_content = f.read() contents[file] = [ file_content, re.compile( rf'(^|\n)import +[\w., ]*\b{file[:-3]}\b[\w., ]*', re.M), re.compile( rf'(^|\n)from +\.? *\b{file[:-3]}\b +import +', re.M) ] _contents_cached[path] = contents[file] is_imported = {f: False for f in files} has_name_main = {f: False for f in files} for file in files: source = contents[file][0] if '__name__' in source and '__main__' in source: has_name_main[file] = True for f, (s, r1, r2) in contents.items(): if r1.search(source) is not None or r2.search( source) is not None: is_imported[f] = True candidates_by_import = [f for f in files if not is_imported[f]] if len(candidates_by_import) == 1: return folder, candidates_by_import[0] candidates_by_name_main = [f for f in files if has_name_main[f]] if len(candidates_by_name_main) == 1: return folder, candidates_by_name_main[0] candidates_import_main = [ f for f in candidates_by_import if has_name_main[f] ] if len(candidates_import_main) == 1: return folder, candidates_import_main[0] if len(candidates_import_main) > 1: str_files = ', '.join(f'"{f}"' for f in candidates_import_main) raise ErrorWithFeedback( f'Cannot decide which file to run out of the following: {str_files}\n' 'They all have "if __name__ == \'__main__\'". Leave one file with this line.' ) str_files = ', '.join(f'"{f}"' for f in ( candidates_by_import if len(candidates_by_import) else files)) raise ErrorWithFeedback( f'Cannot decide which file to run out of the following: {str_files}\n' 'Write "if __name__ == \'__main__\'" in one of them to mark it as an entry point.' ) raise ErrorWithFeedback( 'Cannot find a file to import and run your code.\n' f'Are your project files located at \"{curr_folder}\"?')
def _check_errors(self): if self.process.is_error_happened(): self.process.terminate() raise ErrorWithFeedback(self.process.stderr)
def test(self): status, feedback = TestRunTestInsideTest('main').run_tests() if status != 0: raise ErrorWithFeedback(feedback) return CheckResult.correct()
def _check_errors(self): for process_item in self.processes: filename, process = process_item if process.is_error_happened(): raise ErrorWithFeedback( f'Error running "{filename}"\n\n{process.stderr}')
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