コード例 #1
0
    def get_repo_urls(
        self,
        assignment_names: Iterable[str],
        org_name: Optional[str] = None,
        team_names: Optional[List[str]] = None,
        insert_auth: bool = False,
    ) -> List[str]:
        """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`."""
        with _try_api_request():
            org = (
                self._org
                if not org_name
                else self._github.get_organization(org_name)
            )

        scheme, netloc, org_path, *_ = urllib.parse.urlsplit(org.html_url)
        base_html_url = urllib.parse.urlunsplit([scheme, netloc, *([""] * 3)])

        repo_names = (
            assignment_names
            if not team_names
            else plug.generate_repo_names(team_names, assignment_names)
        )
        repo_urls = [
            urllib.parse.urljoin(base_html_url, f"{org_path}/{repo_name}")
            for repo_name in list(repo_names)
        ]
        return list(
            repo_urls if not insert_auth else map(self.insert_auth, repo_urls)
        )
コード例 #2
0
ファイル: test_repos.py プロジェクト: repobee/repobee
    def test_use_extended_students_syntax(self, platform_url, tmp_path):
        students_file = tmp_path / "students.yml"
        students_file.write_text(
            """
some-team:
    members: [simon]
other-team:
    members: [eve, alice]
        """.strip(),
            encoding=sys.getdefaultencoding(),
        )
        expected_repo_names = plug.generate_repo_names(
            team_names=["some-team", "other-team"],
            assignment_names=const.TEMPLATE_REPO_NAMES,
        )

        funcs.run_repobee(
            f"{plug.cli.CoreCommand.repos.setup} --base-url {platform_url} "
            f"--students-file {students_file} "
            f"--assignments {const.TEMPLATE_REPOS_ARG}",
            plugins=[_repobee.ext.studentsyml],
        )

        actual_repo_names = [
            repo.name for repo in funcs.get_repos(platform_url)
        ]
        assert sorted(actual_repo_names) == sorted(expected_repo_names)
コード例 #3
0
ファイル: test_repos.py プロジェクト: tohanss/repobee
    def test_pylint_plugin_with_python_syntax_error(
        self, platform_url, tmp_path_factory
    ):
        """Test that the pylint plugin correctly reports errors."""
        workdir = tmp_path_factory.mktemp("workdir")
        python_task = self._setup_task_with_faulty_python_code(
            platform_url, workdir
        )
        repo_names = plug.generate_repo_names(
            STUDENT_TEAMS, [python_task.name]
        )

        plug_results = funcs.run_repobee(
            f"repos clone -a {python_task.name} --base-url {platform_url}",
            plugins=[_repobee.ext.pylint],
            workdir=workdir,
        )

        assert plug_results
        assert len(plug_results) == len(repo_names)
        for repo_name in repo_names:
            pylint_result, *_ = plug_results[repo_name]
            assert pylint_result.name == "pylint"
            assert pylint_result.status == plug.Status.ERROR
            assert "src/main.py -- ERROR" in pylint_result.msg
コード例 #4
0
ファイル: test_repos.py プロジェクト: tohanss/repobee
    def test_post_clone_hook_invoked_on_all_student_repos(
        self, platform_url, with_student_repos
    ):
        """Test that the post_clone hook is called with the expected
        repositories.
        """
        expected_repo_names = set(
            plug.generate_repo_names(STUDENT_TEAMS, TEMPLATE_REPO_NAMES)
        )

        class PostClonePlugin(plug.Plugin):
            def post_clone(
                self, repo: plug.StudentRepo, api: plug.PlatformAPI
            ):
                # remove repo names one by one, in the end none should remain
                expected_repo_names.remove(repo.name)

                assert isinstance(api, localapi.LocalAPI)
                assert repo.path.is_dir()

        funcs.run_repobee(
            f"repos clone -a {TEMPLATE_REPOS_ARG} "
            f"--base-url {platform_url}",
            plugins=[PostClonePlugin],
        )

        assert not expected_repo_names
コード例 #5
0
ファイル: test_issues.py プロジェクト: dmusican/repobee
    def test_open_issue_for_all_repos(self, with_student_repos, platform_url,
                                      issue):
        expected_repo_names = plug.generate_repo_names(
            const.STUDENT_TEAMS, const.TEMPLATE_REPO_NAMES)

        funcs.run_repobee(
            f"issues open --assignments {const.TEMPLATE_REPOS_ARG} "
            f"--base-url {platform_url} "
            f"--issue {issue.path} ")

        repos = funcs.get_repos(platform_url, const.TARGET_ORG_NAME)
        issues_dict = {repo.name: repo.issues for repo in repos}

        num_asserts = 0
        for name in expected_repo_names:
            num_asserts += 1
            issues = issues_dict[name]
            first_issue = issues[0]

            assert len(issues) == 1
            assert first_issue.title == issue.title
            assert first_issue.body == issue.body
            assert first_issue.state == plug.IssueState.OPEN

        assert num_asserts == len(expected_repo_names)
コード例 #6
0
 def get_repo_urls(
     self,
     assignment_names: Iterable[str],
     org_name: Optional[str] = None,
     team_names: Optional[List[str]] = None,
     insert_auth: bool = False,
 ) -> List[str]:
     """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`."""
     with _try_api_request():
         org = (
             self._org
             if not org_name
             else self._github.get_organization(org_name)
         )
     repo_names = (
         assignment_names
         if not team_names
         else plug.generate_repo_names(team_names, assignment_names)
     )
     return [
         self.insert_auth(url) if insert_auth else url
         for url in (
             "{}/{}".format(org.html_url, repo_name)
             for repo_name in list(repo_names)
         )
     ]
コード例 #7
0
def with_multi_issues_file(tmp_path):
    """Create the multi issues file."""
    repo_names = plug.generate_repo_names(STUDENT_TEAM_NAMES, ASSIGNMENT_NAMES)
    repos_and_issues = [(repo_name, random.choice(ISSUES))
                        for repo_name in repo_names]
    issues_file = tmp_path / "issues.md"
    _write_multi_issues_file(repos_and_issues, issues_file)
    return issues_file, repos_and_issues
コード例 #8
0
def _generate_multi_issues_file_content(students: List[str],
                                        assignments: List[str]) -> str:

    issue_headers = [
        f"#ISSUE#{repo_name}#<ISSUE-TITLE>\n<ISSUE-BODY>"
        for repo_name in plug.generate_repo_names(students, assignments)
    ]

    return "\n\n".join(issue_headers)
コード例 #9
0
def assert_repos_exist(student_teams, assignment_names, org_name=ORG_NAME):
    """Assert that the associated student repos exist."""
    repo_names = plug.generate_repo_names(student_teams, assignment_names)
    gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False)
    target_group = get_group(org_name, gl=gl)
    student_groups = gl.groups.list(id=target_group.id)

    projects = [p for g in student_groups for p in g.projects.list(all=True)]
    project_names = [p.name for p in projects]

    assert set(project_names) == set(repo_names)
コード例 #10
0
def _filter_hook_results(hook_results_mapping, teams, assignment_names):
    """Return an OrderedDict of hook result mappings for which the repo name is
    contained in the cross product of teams and master repo names.
    """
    repo_names = set(plug.generate_repo_names(teams, assignment_names))
    selected_hook_results = collections.OrderedDict()
    for repo_name, hook_results in sorted(hook_results_mapping.items()):
        if repo_name in repo_names:
            selected_hook_results[repo_name] = hook_results
    missing_repo_names = repo_names - selected_hook_results.keys()
    _log_missing_repo_names(missing_repo_names)
    return selected_hook_results
コード例 #11
0
ファイル: localapi.py プロジェクト: tohanss/repobee
 def get_repo_urls(
     self,
     assignment_names: Iterable[str],
     org_name: Optional[str] = None,
     team_names: Optional[List[str]] = None,
     insert_auth: bool = False,
 ) -> List[str]:
     assert not insert_auth, "not yet implemented"
     base = self._repodir / (org_name or self._org_name)
     repo_names = (assignment_names if not team_names else
                   plug.generate_repo_names(team_names, assignment_names))
     return [(base / name).as_uri() for name in repo_names]
コード例 #12
0
def with_issues(tmp_path):
    """Create issue files in a temporary directory and return a list of (team,
    issue) tuples.
    """
    repo_names = plug.generate_repo_names(STUDENT_TEAM_NAMES, ASSIGNMENT_NAMES)
    existing_issues = []
    for repo_name in repo_names:
        issue_file = tmp_path / "{}.md".format(repo_name)
        issue = random.choice(ISSUES)
        _write_issue(issue, issue_file)
        existing_issues.append((repo_name, issue))
    return existing_issues
コード例 #13
0
ファイル: conftest.py プロジェクト: repobee/repobee
 def get_repo_urls(
     self,
     assignment_names: Iterable[str],
     org_name: Optional[str] = None,
     team_names: Optional[List[str]] = None,
     insert_auth: bool = False,
 ) -> List[str]:
     repo_names = (assignment_names if not team_names else
                   plug.generate_repo_names(team_names, assignment_names))
     return [
         f"{constants.HOST_URL}/{org_name or self.org_name}/{repo_name}"
         for repo_name in repo_names
     ]
コード例 #14
0
def _assert_on_projects(student_teams, assignment_names, assertion):
    """Execute the specified assertion operation on a project. Assertion should
    be a callable taking precisely on project as an argument.
    """
    gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False)
    repo_names = plug.generate_repo_names(student_teams, assignment_names)
    target_group = get_group(ORG_NAME, gl=gl)
    student_groups = gl.groups.list(id=target_group.id)
    projects = [
        gl.projects.get(p.id) for g in student_groups
        for p in g.projects.list(all=True) if p.name in repo_names
    ]

    for proj in projects:
        assertion(proj)
コード例 #15
0
ファイル: gitea.py プロジェクト: repobee/repobee
 def get_repo_urls(
     self,
     assignment_names: Iterable[str],
     org_name: Optional[str] = None,
     team_names: Optional[List[str]] = None,
     insert_auth: bool = False,
 ) -> List[str]:
     """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`."""
     org_html_url = self._org_base_url(org_name or self._org_name)
     repo_names = (assignment_names if not team_names else
                   plug.generate_repo_names(team_names, assignment_names))
     return [
         self.insert_auth(url) if insert_auth else url
         for url in (f"{org_html_url}/{repo_name}.git"
                     for repo_name in list(repo_names))
     ]
コード例 #16
0
ファイル: test_repos.py プロジェクト: repobee/repobee
    def test_javac_plugin_happy_path(self, platform_url, tmp_path):
        java_task = self._setup_task_with_java_code(platform_url, tmp_path)
        repo_names = plug.generate_repo_names(STUDENT_TEAMS, [java_task.name])

        plug_results = funcs.run_repobee(
            f"repos clone -a {java_task.name} --base-url {platform_url} ",
            plugins=[_repobee.ext.javac],
            workdir=tmp_path,
        )

        assert plug_results
        assert len(plug_results) == len(repo_names)
        for repo_name in repo_names:
            javac_result, *_ = plug_results[repo_name]
            assert javac_result.name == "javac"
            assert javac_result.status == plug.Status.SUCCESS
コード例 #17
0
ファイル: test_github.py プロジェクト: repobee/repobee
    def test_with_students(self, repos, api):
        """Test that supplying students causes student repo names to be
        generated as the Cartesian product of the supplied repo names and the
        students.
        """
        students = list(constants.STUDENTS)
        assignment_names = [repo.name for repo in repos]
        expected_repo_names = plug.generate_repo_names(students,
                                                       assignment_names)
        # assume works correctly when called with just repo names
        expected_urls = api.get_repo_urls(expected_repo_names)

        actual_urls = api.get_repo_urls(assignment_names,
                                        team_names=[t.name for t in students])

        assert len(actual_urls) == len(students) * len(assignment_names)
        assert sorted(expected_urls) == sorted(actual_urls)
コード例 #18
0
    def test_lists_matching_issues(
        self, open_issues, extra_args, discover_repos
    ):
        # arrange
        assert len(open_issues) == 2, "expected there to be only 2 open issues"
        matched = open_issues[0]
        unmatched = open_issues[1]
        repo_names = plug.generate_repo_names(STUDENT_TEAMS, assignment_names)

        issue_pattern_template = r"^.*{}/#\d:\s+{}.*by {}.?$"
        expected_issue_output_patterns = [
            issue_pattern_template.format(repo_name, matched.title, TEACHER)
            for repo_name in repo_names
        ]
        unexpected_issue_output_patterns = [
            issue_pattern_template.format(repo_name, unmatched.title, TEACHER)
            for repo_name in repo_names
        ] + [
            r"\[ERROR\]"
        ]  # any kind of error is bad

        repo_arg = ["--discover-repos"] if discover_repos else MASTER_REPOS_ARG
        command = " ".join(
            [
                REPOBEE_GITLAB,
                *repobee_plug.cli.CoreCommand.issues.list.as_name_tuple(),
                *BASE_ARGS,
                *repo_arg,
                *STUDENTS_ARG,
                "-r",
                matched.title,
            ]
        )

        # act
        result = run_in_docker_with_coverage(command, extra_args=extra_args)
        output = result.stdout.decode("utf-8")

        # assert
        assert result.returncode == 0
        search_flags = re.MULTILINE
        for expected_pattern in expected_issue_output_patterns:
            assert re.search(expected_pattern, output, search_flags)
        for unexpected_pattern in unexpected_issue_output_patterns:
            assert not re.search(unexpected_pattern, output, search_flags)
コード例 #19
0
ファイル: test_repos.py プロジェクト: repobee/repobee
    def test_pylint_plugin_happy_path(self, platform_url, tmp_path):
        python_task = self._setup_task_with_python_code(platform_url, tmp_path)
        repo_names = plug.generate_repo_names(STUDENT_TEAMS,
                                              [python_task.name])

        plug_results = funcs.run_repobee(
            f"repos clone -a {python_task.name} --base-url {platform_url}",
            plugins=[_repobee.ext.pylint],
            workdir=tmp_path,
        )

        assert plug_results
        assert len(plug_results) == len(repo_names)
        for repo_name in repo_names:
            pylint_result, *_ = plug_results[repo_name]
            assert pylint_result.name == "pylint"
            assert pylint_result.status == plug.Status.SUCCESS
            assert "src/main.py -- OK" in pylint_result.msg
コード例 #20
0
ファイル: test_issues.py プロジェクト: dmusican/repobee
    def test_list_double_blind_issues(self, platform_url, with_student_repos,
                                      capsys):
        key = "1234"
        assignment = const.TEMPLATE_REPO_NAMES[0]
        review_title = "This is the peer review"
        _setup_double_blind_reviews_with_review_issues(assignment, key,
                                                       platform_url,
                                                       review_title)

        funcs.run_repobee(f"issues list --assignments {assignment} "
                          f"--double-blind-key {key} "
                          f"--base-url {platform_url}")

        stdout = capsys.readouterr().out
        expected_repo_names = plug.generate_repo_names(const.STUDENT_TEAMS,
                                                       [assignment])
        for repo_name in expected_repo_names:
            assert re.search(fr"{repo_name}.*{review_title}", stdout)
コード例 #21
0
    def test_open_issues_from_double_blind_hook_results(
        self, with_student_repos, platform_url, tmp_path
    ):
        """Test opening issues from a hook results file gathered from listing
        issues from double-blind peer review.
        """

        # arrange
        results_file = tmp_path / "hf.json"
        key = "1234"
        assignment = const.TEMPLATE_REPO_NAMES[0]
        review_title = "This is the peer review"
        _setup_double_blind_reviews_with_review_issues(
            assignment, key, platform_url, review_title
        )
        funcs.run_repobee(
            f"issues list --assignments {assignment} "
            f"--double-blind-key {key} "
            f"--base-url {platform_url} "
            f"--hook-results-file {results_file}"
        )

        # act
        funcs.run_repobee(
            f"issues open --assignments {assignment} "
            f"--base-url {platform_url} "
            f"--hook-results-file {results_file}"
        )

        # assert
        expected_repo_names = set(
            plug.generate_repo_names(
                [t.name for t in const.STUDENT_TEAMS], [assignment]
            )
        )
        repos = [
            repo
            for repo in funcs.get_repos(platform_url)
            if repo.name in expected_repo_names
        ]
        assert repos
        for repo in repos:
            assert len(repo.issues) == 2
            assert review_title in [i.title for i in repo.issues]
コード例 #22
0
def assert_repos_contain(student_teams,
                         assignment_names,
                         filename,
                         text,
                         org=ORG_NAME):
    """Assert that each of the student repos contain the given file."""
    repo_names = plug.generate_repo_names(student_teams, assignment_names)
    gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False)
    target_group = get_group(org, gl=gl)
    student_groups = gl.groups.list(id=target_group.id)

    projects = [
        gl.projects.get(p.id) for g in student_groups
        for p in g.projects.list(all=True) if p.name in repo_names
    ]
    assert len(projects) == len(repo_names)
    for project in projects:
        assert (project.files.get(filename,
                                  "master").decode().decode("utf8") == text)
コード例 #23
0
ファイル: test_repos.py プロジェクト: repobee/repobee
def _assert_repos_match_templates(
    student_teams: List[plug.StudentTeam],
    template_repo_names: List[str],
    template_repo_hashes: Mapping[str, str],
    repos_dict: Mapping[str, pathlib.Path],
):
    num_asserts = 0
    for template_name in template_repo_names:
        student_repos = [
            repos_dict[repo_name] for repo_name in plug.generate_repo_names(
                student_teams, [template_name])
        ]
        assert len(student_repos) == len(student_teams)

        for repo in student_repos:
            num_asserts += 1
            assert funcs.tree_hash(repo) == template_repo_hashes[template_name]

    assert num_asserts == len(student_teams) * len(
        template_repo_names), "Performed fewer asserts than expected"
コード例 #24
0
ファイル: test_repos.py プロジェクト: repobee/repobee
    def test_clone_all_repos_flat(self, platform_url, with_student_repos,
                                  tmp_path):
        """Test that cloning with flat directory layout results in all
        repositories ending up in the current working directory.
        """
        expected_dirnames = plug.generate_repo_names(STUDENT_TEAMS,
                                                     TEMPLATE_REPO_NAMES)

        funcs.run_repobee(
            f"repos clone -a {TEMPLATE_REPOS_ARG} "
            f"--base-url {platform_url} "
            "--directory-layout "
            f"{fileutil.DirectoryLayout.FLAT}",
            workdir=tmp_path,
        )

        actual_dirnames = [
            path.name for path in tmp_path.iterdir() if path.is_dir()
        ]
        assert sorted(actual_dirnames) == sorted(expected_dirnames)
コード例 #25
0
ファイル: test_gitlab_system.py プロジェクト: repobee/repobee
    def test_lists_matching_issues(self, open_issues, tmpdir, discover_repos):
        # arrange
        assert len(open_issues) == 2, "expected there to be only 2 open issues"
        matched = open_issues[0]
        unmatched = open_issues[1]
        repo_names = plug.generate_repo_names(STUDENT_TEAMS, assignment_names)

        issue_pattern_template = r"^.*{}/#\d:\s+{}.*by {}.?$"
        expected_issue_output_patterns = [
            issue_pattern_template.format(repo_name, matched.title, TEACHER)
            for repo_name in repo_names
        ]
        unexpected_issue_output_patterns = [
            issue_pattern_template.format(repo_name, unmatched.title, TEACHER)
            for repo_name in repo_names
        ] + [r"\[ERROR\]"]  # any kind of error is bad

        repo_arg = ["--discover-repos"] if discover_repos else MASTER_REPOS_ARG
        command = " ".join([
            *repobee_plug.cli.CoreCommand.issues.list.as_name_tuple(),
            *BASE_ARGS,
            *repo_arg,
            *STUDENTS_ARG,
            "-r",
            matched.title,
        ])

        # act
        with contextlib.redirect_stdout(io.StringIO()) as sio:
            run_repobee(command, workdir=tmpdir, plugins=[_repobee.ext.gitlab])

        output = sio.getvalue()

        # assert
        search_flags = re.MULTILINE
        for expected_pattern in expected_issue_output_patterns:
            assert re.search(expected_pattern, output, search_flags)
        for unexpected_pattern in unexpected_issue_output_patterns:
            assert not re.search(unexpected_pattern, output, search_flags)
コード例 #26
0
    def test_skips_unexpected_issues_in_multi_issues_file(
            self, with_multi_issues_file, parsed_args_multi_issues_file,
            api_mock):
        """Test that an exception is raised if one or more issues are found
        relating to student repos that ar not in prod(assignments, students).
        """
        student_teams = parsed_args_multi_issues_file.students
        args_dict = vars(parsed_args_multi_issues_file)
        args_dict["students"] = student_teams[:-1]
        args = argparse.Namespace(**args_dict)
        unexpected_repos = plug.generate_repo_names(student_teams[-1:],
                                                    ASSIGNMENT_NAMES)

        _, repos_and_issues = with_multi_issues_file
        expected_calls = [
            mock.call(issue.title, issue.body, mock.ANY)
            for repo_name, issue in repos_and_issues
            if repo_name not in unexpected_repos
        ]

        feedback.callback(args=args, api=api_mock)

        api_mock.create_issue.assert_has_calls(expected_calls, any_order=True)
コード例 #27
0
    def test_use_generated_reference_tests_directory(
        self,
        tmp_path_factory,
        platform_url,
        setup_student_repos,
        workdir,
        rtd_path,
    ):
        """Test using a generated RTD with the clone command."""
        # arrange
        run_generate_rtd(base_url=platform_url, rtd=rtd_path, workdir=workdir)
        clone_dir = workdir / "clone_dir"
        clone_dir.mkdir()

        # act
        results = repobee_testhelpers.funcs.run_repobee(
            f"repos clone -a {ASSIGNMENTS_ARG} "
            f"--base-url {platform_url} "
            f"--junit4-reference-tests-dir {rtd_path} "
            f"--junit4-hamcrest-path {HAMCREST_PATH} "
            f"--junit4-junit-path {JUNIT_PATH} ",
            plugins=[junit4],
            workdir=clone_dir,
        )

        # assert
        iterations = 0
        for repo_name in plug.generate_repo_names(
                repobee_testhelpers.const.STUDENT_TEAMS, ASSIGNMENT_NAMES):
            iterations += 1
            first_result, *rest = results[repo_name]
            assert not rest, "there should only be one result"
            assert first_result.name == SECTION
            assert first_result.status != plug.Status.ERROR

        assert iterations > 0, "the assertion loop did not execute"
コード例 #28
0
def check_peer_review_progress(
    assignment_names: Iterable[str],
    teams: Iterable[plug.Team],
    title_regex: str,
    num_reviews: int,
    double_blind_key: Optional[str],
    api: plug.PlatformAPI,
) -> None:
    """Check which teams have opened peer review issues in their allotted
    review repos

    Args:
        assignment_names: Names of assignments.
        teams: An iterable of student teams.
        title_regex: A regex to match against issue titles.
        num_reviews: Amount of reviews each student is expected to have made.
        api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to
            interface with the platform (e.g. GitHub or GitLab) instance.

    """
    teams = list(teams)
    reviews = collections.defaultdict(list)

    review_team_names = [
        _review_team_name(student_team, assignment_name, double_blind_key)
        for student_team in teams for assignment_name in assignment_names
    ]
    rainbow_table = {
        _hash_if_key(repo_name, key=double_blind_key): repo_name
        for repo_name in plug.generate_repo_names(teams, assignment_names)
    }

    review_teams = progresswrappers.get_teams(review_team_names,
                                              api,
                                              desc="Processing review teams")
    for review_team in review_teams:
        repos = list(api.get_team_repos(review_team))
        if len(repos) != 1:
            plug.log.warning(
                f"Expected {review_team.name} to have 1 associated "
                f"repo, found {len(repos)}. "
                f"Skipping...")
            continue

        reviewed_repo = repos[0]
        expected_reviewers = set(review_team.members)
        reviewing_teams = _extract_reviewing_teams(teams, expected_reviewers)

        review_issue_authors = {
            issue.author
            for issue in api.get_repo_issues(reviewed_repo)
            if re.match(title_regex, issue.title)
        }

        for team in reviewing_teams:
            reviews[str(team)].append(
                plug.Review(
                    repo=rainbow_table[reviewed_repo.name],
                    done=any(
                        map(review_issue_authors.__contains__, team.members)),
                ))

    plug.echo(
        formatters.format_peer_review_progress_output(
            reviews, [team.name for team in teams], num_reviews))
コード例 #29
0
def assign_peer_reviews(
    assignment_names: Iterable[str],
    teams: Iterable[plug.StudentTeam],
    num_reviews: int,
    issue: Optional[plug.Issue],
    double_blind_key: Optional[str],
    api: plug.PlatformAPI,
) -> None:
    """Assign peer reviewers among the students to each student repo. Each
    student is assigned to review num_reviews repos, and consequently, each
    repo gets reviewed by num_reviews reviewers.

    In practice, each student repo has a review team generated (called
    <student-repo-name>-review), to which num_reviews _other_ students are
    assigned. The team itself is given pull-access to the student repo, so
    that reviewers can view code and open issues, but cannot modify the
    contents of the repo.

    Args:
        assignment_names: Names of assginments.
        teams: Team objects specifying student groups.
        num_reviews: Amount of reviews each student should perform
            (consequently, the amount of reviews of each repo)
        issue: An issue with review instructions to be opened in the considered
            repos.
        double_blind_key: If provided, use key to make double-blind review
            allocation.
        api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to
            interface with the platform (e.g. GitHub or GitLab) instance.
    """
    issue = issue or DEFAULT_REVIEW_ISSUE
    expected_repo_names = set(plug.generate_repo_names(teams,
                                                       assignment_names))
    fetched_teams = progresswrappers.get_teams(teams,
                                               api,
                                               desc="Fetching teams and repos")
    team_repo_tuples = [(team, list(api.get_team_repos(team)))
                        for team in fetched_teams]
    fetched_repos = list(
        itertools.chain.from_iterable(repos for _, repos in team_repo_tuples))
    fetched_repo_dict = {r.name: r for r in fetched_repos}

    missing = expected_repo_names - fetched_repo_dict.keys()
    if missing:
        raise plug.NotFoundError(f"Can't find repos: {', '.join(missing)}")

    if double_blind_key:
        plug.log.info(f"Creating anonymous repos with key: {double_blind_key}")
        fetched_repo_dict = _create_anonymized_repos(
            [(team, _only_expected_repos(repos, expected_repo_names))
             for team, repos in team_repo_tuples],
            double_blind_key,
            api,
        )

    allocations_for_output = []
    for assignment_name in assignment_names:
        plug.echo("Allocating reviews")
        allocations = plug.manager.hook.generate_review_allocations(
            teams=teams, num_reviews=num_reviews)
        # adjust names of review teams
        review_team_specs, reviewed_team_names = list(
            zip(*[(
                plug.StudentTeam(
                    members=alloc.review_team.members,
                    name=_review_team_name(
                        alloc.reviewed_team,
                        assignment_name,
                        key=double_blind_key,
                    ),
                ),
                alloc.reviewed_team,
            ) for alloc in allocations]))

        review_teams = _repobee.command.teams.create_teams(
            review_team_specs, plug.TeamPermission.PULL, api)
        review_teams_progress = plug.cli.io.progress_bar(
            review_teams,
            desc="Creating review teams",
            total=len(review_team_specs),
        )

        for review_team, reviewed_team_name in zip(review_teams_progress,
                                                   reviewed_team_names):
            reviewed_repo = fetched_repo_dict[plug.generate_repo_name(
                reviewed_team_name, assignment_name)]

            review_teams_progress.write(  # type: ignore
                f"Assigning {' and '.join(review_team.members)} "
                f"to review {reviewed_repo.name}")
            api.assign_repo(review_team, reviewed_repo,
                            plug.TeamPermission.PULL)
            api.create_issue(
                issue.title,
                issue.body,
                reviewed_repo,
                # It's not possible to assign users with read-access in Gitea
                # FIXME redesign so Gitea does not require special handling
                assignees=review_team.members
                if not isinstance(api, _repobee.ext.gitea.GiteaAPI) else None,
            )

            allocations_for_output.append({
                "reviewed_repo": {
                    "name": reviewed_repo.name,
                    "url": reviewed_repo.url,
                },
                "review_team": {
                    "name": review_team.name,
                    "members": review_team.members,
                },
            })

        if featflags.is_feature_enabled(
                featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS):
            output = dict(allocations=allocations_for_output,
                          num_reviews=num_reviews)
            pathlib.Path("review_allocations.json").write_text(
                json.dumps(output, indent=4),
                encoding=sys.getdefaultencoding(),
            )
コード例 #30
0
import pathlib

import repobee_plug as plug

from repobee_testhelpers._internal.templates import TEMPLATE_REPOS_DIR

DIR = pathlib.Path(__file__).resolve().parent
TOKEN = (DIR.parent / "token").read_text(encoding="utf-8").strip()
ADMIN_TOKEN = "".join(reversed(TOKEN))
OAUTH_USER = "******"
BASE_DOMAIN = "localhost:3000"
BASE_URL = "https://" + BASE_DOMAIN
ORG_NAME = "dd1337-fall2020"
TEMPLATE_ORG_NAME = "dd1337-master"
TEACHER = "ric"
assignment_names = [p.name for p in TEMPLATE_REPOS_DIR.iterdir() if p.is_dir()]
TEMPLATE_REPO_PATHS = list(dir_.absolute()
                           for dir_ in TEMPLATE_REPOS_DIR.iterdir()
                           if dir_.is_dir())
STUDENT_TEAMS = [
    plug.StudentTeam(members=[s.strip()])
    for s in (DIR.parent / "students.txt").read_text().strip().split("\n")
]
STUDENT_TEAM_NAMES = [str(t) for t in STUDENT_TEAMS]
STUDENT_REPO_NAMES = plug.generate_repo_names(STUDENT_TEAMS, assignment_names)
BASE_ARGS_NO_TB = ["--bu", BASE_URL, "-o", ORG_NAME, "-t", TOKEN]
BASE_ARGS = [*BASE_ARGS_NO_TB, "--tb"]
STUDENTS_ARG = ["-s", " ".join(STUDENT_TEAM_NAMES)]
MASTER_REPOS_ARG = ["-a", " ".join(assignment_names)]
TEMPLATE_ORG_ARG = ["--template-org-name", TEMPLATE_ORG_NAME]