def act_on_cloned_repo(self, path: Union[str, pathlib.Path], api: plug.API) -> plug.HookResult: """List all files in a cloned repo. Args: path: Path to the student repo. api: An instance of :py:class:`repobee_plug.API`. Returns: a plug.HookResult specifying the outcome. """ out = [] pattern = "*.java" try: path = pathlib.Path(path) if not path.exists(): return plug.HookResult( hook=PLUGIN_NAME, status=plug.Status.ERROR, msg=f"student repo {path!s} does not exist", ) # error on rglob filepaths = [ p for p in path.resolve().rglob(pattern) if ".git" not in str(p).split(os.sep) ] for path in filepaths: out.append(str(path)) from_encoding = "iso-8859-1" to_encoding = "utf-8" # error when encoding is wrong with path.open(mode="r", encoding=from_encoding) as f: content = f.read() # ValueError if path doesn't have a name tempfile = path.with_name(path.stem + "_tmp" + path.suffix) tempfile.write_text(content, encoding=to_encoding) #tempfile.replace(path) output = os.linesep.join(out) return plug.HookResult(hook=PLUGIN_NAME, status=plug.Status.SUCCESS, msg=output) except Exception as exc: return plug.HookResult( hook=PLUGIN_NAME, status=plug.Status.ERROR, msg=str(exc), )
def test_raises_if_state_is_not_all(self, tmp_grades_file, mocked_hook_results): """Test that a warning is issued if the plugin is run on list-issues results where the state is not ``all`` (i.e. ``repobee list-issues`` was not run with the ``--all`` flag). This is important as closed issues should still be taken into account. """ args = argparse.Namespace( students=list(TEAMS), hook_results_file="", # don't care, read_results_file is mocked grades_file=str(tmp_grades_file), master_repo_names="week-1 week-2 week-4 week-6".split(), edit_msg_file=str(tmp_grades_file.parent / "editmsg.txt"), teachers=list(TEACHERS), grade_specs=[PASS_GRADESPEC_FORMAT], allow_other_states=False, ) mocked_hook_results["list-issues"] = [ plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, # change the state to OPEN, which will cause any closed # grading issues to be missed data={"state": plug.IssueState.OPEN.value}, ) ] with pytest.raises(_exception.FileError) as exc_info: csvgrades.callback(args=args, api=None) assert "repobee list-issues was not run with the --all flag" in str( exc_info.value)
def pairwise_compile( test_classes: List[pathlib.Path], java_files: List[pathlib.Path], classpath: str, ) -> Tuple[List[plug.HookResult], List[plug.HookResult]]: """Compile test classes with their associated production classes. For each test class: 1. Find the associated production class among the ``java_files`` 2. Compile the test class together with all of the .java files in the associated production class' directory. Args: test_classes: A list of paths to test classes. java_files: A list of paths to java files from the student repo. classpath: A base classpath to use. Returns: A tuple of lists of HookResults on the form ``(succeeded, failed)`` """ failed = [] succeeded = [] # only use concrete test classes concrete_test_classes = filter(lambda t: not is_abstract_class(t), test_classes) for test_class in concrete_test_classes: status, msg, prod_class_path = _pairwise_compile( test_class, classpath, java_files) if status != Status.SUCCESS: failed.append(plug.HookResult(SECTION, status, msg)) else: succeeded.append((test_class, prod_class_path)) return succeeded, failed
def act_on_cloned_repo(self, path: Union[str, pathlib.Path]) -> plug.HookResult: """Look for production classes in the student repo corresponding to test classes in the reference tests directory. Assumes that all test classes end in ``Test.java`` and that there is a directory with the same name as the master repo in the reference tests directory. Args: path: Path to the student repo. Returns: a plug.HookResult specifying the outcome. """ if not pathlib.Path(self._reference_tests_dir).is_dir(): raise plug.exception.PlugError("{} is not a directory".format( self._reference_tests_dir)) assert self._master_repo_names assert self._reference_tests_dir try: path = pathlib.Path(path) if not path.exists(): return plug.HookResult( SECTION, Status.ERROR, "student repo {!s} does not exist".format(path), ) compile_succeeded, compile_failed = self._compile_all(path) test_results = self._run_tests(compile_succeeded) has_failures = compile_failed or any( map(lambda r: not r.success, test_results)) msg = _output.format_results(test_results, compile_failed, self._verbose, self._very_verbose) status = (Status.ERROR if compile_failed else (Status.WARNING if has_failures else Status.SUCCESS)) return plug.HookResult(SECTION, status, msg) except _exception.ActError as exc: return exc.hook_result except Exception as exc: return plug.HookResult(SECTION, Status.ERROR, str(exc))
def create_komp_and_pass_hookresult(author): other = create_komp_hookresult(author) pass_ = create_pass_hookresult(author) return plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, data={ **other.data, **pass_.data }, )
def create_komp_hookresult(author): komp_issue = plug.Issue( title="Komplettering", body="This is komplettering", number=1, created_at=datetime(2009, 12, 31), author=author, ) return plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, data={komp_issue.number: komp_issue.to_dict()}, )
def create_pass_hookresult(author): pass_issue = plug.Issue( title="Pass", body="This is a pass", number=3, created_at=datetime(1992, 9, 19), author=author, ) return plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, data={pass_issue.number: pass_issue.to_dict()}, )
def mocked_hook_results(mocker): """Hook results with passes for glassey-glennol in week-1 and week-2, and for slarse in week-4 and week-6. """ slarse, glassey_glennol = TEAMS gen_name = _marker.generate_repo_name hook_results = { gen_name(str(team), repo_name): [result] for team, repo_name, result in [ (slarse, "week-1", create_komp_hookresult(SLARSE_TA)), (slarse, "week-2", create_komp_hookresult(SLARSE_TA)), (slarse, "week-4", create_pass_hookresult(SLARSE_TA)), (slarse, "week-6", create_komp_and_pass_hookresult(SLARSE_TA)), ( glassey_glennol, "week-1", create_pass_hookresult(GLASSEY_GLENNOL_TA), ), ( glassey_glennol, "week-2", create_komp_and_pass_hookresult(GLASSEY_GLENNOL_TA), ), ( glassey_glennol, "week-4", create_komp_hookresult(GLASSEY_GLENNOL_TA), ), ( glassey_glennol, "week-6", create_komp_hookresult(GLASSEY_GLENNOL_TA), ), ] } hook_results["list-issues"] = [ plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, data={"state": plug.IssueState.ALL.value}, ) ] mocker.patch( "repobee_csvgrades._file.read_results_file", return_value=hook_results, autospec=True, ) return hook_results
def _find_test_classes(self, master_name) -> List[pathlib.Path]: """Find all test classes (files ending in ``Test.java``) in directory at <reference_tests_dir>/<master_name>. Args: master_name: Name of a master repo. Returns: a list of test classes from the corresponding reference test directory. """ test_dir = pathlib.Path(self._reference_tests_dir) / master_name if not test_dir.is_dir(): res = plug.HookResult( SECTION, Status.ERROR, "no reference test directory for {} in {}".format( master_name, self._reference_tests_dir), ) raise _exception.ActError(res) test_classes = [ file for file in test_dir.rglob("*.java") if file.name.endswith("Test.java") and file.name not in self._ignore_tests ] if not test_classes: res = plug.HookResult( SECTION, Status.WARNING, "no files ending in `Test.java` found in {!s}".format( test_dir), ) raise _exception.ActError(res) return test_classes
def act_on_cloned_repo( path: Union[str, pathlib.Path], api: plug.API ) -> plug.HookResult: """Extract the timestamp of the latest commit message in the student repo at path. Args: path: Path to the student repo. api: An API instance. Always None for this plugin, though. Returns: a plug.HookResult specifying the outcome. """ cmd = 'git log --pretty=format:"%ad" -n 1'.split() proc = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=str(path) ) output = proc.stdout.decode(encoding=SYS_ENCODING) status = plug.Status.SUCCESS if proc.returncode == 0 else plug.Status.ERROR return plug.HookResult(hook=PLUGIN_NAME, status=status, msg=output)
def _extract_master_repo_name(self, path: pathlib.Path) -> str: """Extract the master repo name from the student repo at ``path``. For this to work, the corresponding master repo name must be in self._master_repo_names. Args: path: path to the student repo Returns: the name of the associated master repository """ matches = list(filter(path.name.endswith, self._master_repo_names)) if len(matches) == 1: return matches[0] else: msg = ("no master repo name matching the student repo" if not matches else "multiple matching master repo names: {}".format( ", ".join(matches))) res = plug.HookResult(SECTION, Status.ERROR, msg) raise _exception.ActError(res)
def test_does_not_overwrite_lower_priority_grades(self, tmp_grades_file): """Test that e.g. a grade with priority 3 does not overwrite a grade with priority 1 that is already in the grades file. """ shutil.copy(str(EXPECTED_GRADES_MULTI_SPEC_FILE), str(tmp_grades_file)) slarse, *_ = TEAMS hook_result_mapping = { _marker.generate_repo_name(str(slarse), "week-4"): [create_komp_hookresult(SLARSE_TA)], "list-issues": [ plug.HookResult( hook="list-issues", status=plug.Status.SUCCESS, msg=None, data={"state": plug.IssueState.ALL.value}, ) ], } grades_file_contents = tmp_grades_file.read_text("utf8") edit_msg_file = tmp_grades_file.parent / "editmsg.txt" args = argparse.Namespace( students=[slarse], hook_results_file="", # don't care, read_results_file is mocked grades_file=str(tmp_grades_file), master_repo_names=["week-4"], edit_msg_file=str(edit_msg_file), teachers=list(TEACHERS), grade_specs=[PASS_GRADESPEC_FORMAT, KOMP_GRADESPEC_FORMAT], allow_other_states=False, ) with mock.patch( "repobee_csvgrades._file.read_results_file", autospec=True, return_value=hook_result_mapping, ): csvgrades.callback(args=args, api=None) assert tmp_grades_file.read_text("utf8") == grades_file_contents assert not edit_msg_file.exists()
def __init__(self, msg): res = plug.HookResult(hook=SECTION, status=plug.Status.ERROR, msg=msg) super().__init__(res)