Exemple #1
0
    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)
Exemple #3
0
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
Exemple #4
0
    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
Exemple #9
0
    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
Exemple #10
0
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)
Exemple #11
0
    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()
Exemple #13
0
 def __init__(self, msg):
     res = plug.HookResult(hook=SECTION, status=plug.Status.ERROR, msg=msg)
     super().__init__(res)