Ejemplo n.º 1
0
    def test_opens_issue_when_push_fails(self, platform_url,
                                         with_student_repos, tmp_path):
        """Test running update when a student repo has been modified such that
        the push is rejected. The specified issues should then be opened in
        that student's repo, but not in any of the others.
        """
        # arrange
        title = "You done goofed"
        body = "You need to fix these things manually."
        issue_path = tmp_path / "issue.md"
        issue_path.write_text(f"{title}\n{body}", encoding="utf8")

        # modify a student repo
        repo_path = tmp_path / "repo"
        selected_repo = funcs.get_repos(platform_url)[0]
        repo = git.Repo.clone_from(selected_repo.path, to_path=repo_path)
        repo.git.commit("--amend", "-m", "Best commit")
        repo.git.push("--force")

        # act
        funcs.run_repobee(f"repos update -a {const.TEMPLATE_REPOS_ARG} "
                          f"--base-url {platform_url} "
                          f"--issue {issue_path}")

        # assert
        for platform_repo in funcs.get_repos(platform_url):
            if platform_repo.name == selected_repo.name:
                assert len(platform_repo.issues) == 1
                issue = platform_repo.issues[0]
                assert issue.title == title
                assert issue.body == body
            else:
                assert not platform_repo.issues
Ejemplo n.º 2
0
    def test_use_strange_default_branch_name(self, platform_url):
        strange_branch_name = "definitelynotmaster"

        with tempfile.TemporaryDirectory() as tmpdir:
            template_repo_dir = pathlib.Path(tmpdir)
            task_99 = template_repo_dir / "task-99"
            create_local_repo(
                task_99,
                [("README.md", "Read me plz.")],
                default_branch=strange_branch_name,
            )

            funcs.run_repobee(
                f"repos migrate -a {task_99.name} "
                f"--base-url {platform_url} "
                "--allow-local-templates",
                workdir=template_repo_dir,
            )

            platform_repos = funcs.get_repos(platform_url)
            assert len(platform_repos) == 1
            repo = git.Repo(funcs.get_repos(platform_url)[0].path)

            assert len(repo.branches) == 1
            assert repo.branches[0].name == strange_branch_name
Ejemplo n.º 3
0
def test_create_repo_with_plugin(platform_url):
    team = const.STUDENT_TEAMS[0]
    repo_name = "super-repo"
    description = "This is the description"
    private = True

    class CreateSingle(plug.Plugin, plug.cli.Command):
        __settings__ = plug.cli.command_settings(
            category=plug.cli.CoreCommand.repos, action="create-single")
        team_name = plug.cli.option()
        repo_name = plug.cli.option()

        def command(self, api: plug.PlatformAPI):
            team = api.get_teams(team_names=[self.team_name])
            api.create_repo(
                self.repo_name,
                description=description,
                private=private,
                team=team,
            )

    funcs.run_repobee(
        f"repos create-single --bu {platform_url} "
        f"--team-name {team.name} --repo-name {repo_name}",
        plugins=[CreateSingle],
    )

    existing_repos = funcs.get_repos(platform_url)

    matching_repo = next(
        (repo for repo in existing_repos if repo.name == repo_name), None)

    assert matching_repo.name == repo_name
    assert matching_repo.description == description
    assert matching_repo.private == private
Ejemplo n.º 4
0
    def test_closes_correct_issues(self, with_student_repos, platform_url):
        issue_to_close, open_issue = _open_predefined_issues(platform_url)

        funcs.run_repobee([
            *f"issues close --base-url {platform_url} "
            f"--assignments {const.TEMPLATE_REPOS_ARG} ".split(),
            "--title-regex",
            issue_to_close.title,
        ])

        iterations = 0
        for repo in funcs.get_repos(platform_url, const.TARGET_ORG_NAME):
            iterations += 1
            assert len(repo.issues) == 2
            actual_open_issue, *_ = [
                i for i in repo.issues if i.state == plug.IssueState.OPEN
            ]
            actual_closed_issue, *_ = [
                i for i in repo.issues if i.state == plug.IssueState.CLOSED
            ]
            assert actual_open_issue.title == open_issue.title
            assert actual_closed_issue.title == issue_to_close.title

        assert iterations == len(const.STUDENT_TEAMS) * len(
            const.TEMPLATE_REPO_NAMES)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    def test_does_not_update_local_by_default(self, platform_url,
                                              with_student_repos, tmp_path,
                                              capsys):
        """Test that cloning an update repository that exists locally does not
        cause it to be updated by default.
        """
        # arrange
        new_file_name = "suspicious_file.txt"
        target_repo = funcs.get_repos(platform_url)[-1]
        self._clone_all_student_repos(platform_url, tmp_path)

        with funcs.update_repository(target_repo.url) as repo_path:
            (repo_path / new_file_name).write_text(new_file_name)

        # act
        funcs.run_repobee(
            f"repos clone -a {const.TEMPLATE_REPOS_ARG} "
            f"--base-url {platform_url}",
            workdir=tmp_path,
        )

        # assert
        local_repo_path = list(tmp_path.rglob(target_repo.name))[0]
        local_new_file = local_repo_path / new_file_name
        assert not local_new_file.is_file()
        assert "--update-local" in capsys.readouterr().err
Ejemplo n.º 7
0
def test_add_teachers_command_happy_path(platform_url):
    """The add-teachers command should add all existing repos to the teachers
    team, as well as the specified teachers.
    """
    # arrange
    teachers = ["gork", "mork", "slanesh"]
    setup_student_repos_and_user_accounts(teachers, platform_url)

    # act
    funcs.run_repobee(
        f"teams add-teachers --teachers {' '.join(teachers)} "
        f"--base-url {platform_url}",
        plugins=[tamanager],
    )

    # assert
    teachers_team = get_teachers_team(platform_url)
    num_expected_repos = len(const.STUDENT_TEAMS) * len(
        const.TEMPLATE_REPO_NAMES)
    assert len(teachers_team.repos) == num_expected_repos
    expected_repo_names = [r.name for r in funcs.get_repos(platform_url)]
    actual_repo_names = [r.name for r in teachers_team.repos]
    assert sorted(expected_repo_names) == sorted(actual_repo_names)
    assert sorted([m.username
                   for m in teachers_team.members]) == sorted(teachers)
Ejemplo n.º 8
0
    def test_update_local(self, platform_url, with_student_repos, tmp_path):
        """Test cloning an updated repository that already exists locally, when
        there are no incompatible changes between the remote copy and the local
        copy and --update-local is specified.
        """
        # arrange
        new_file_name = "suspicious_file.txt"
        target_repo = funcs.get_repos(platform_url)[-1]
        self._clone_all_student_repos(platform_url, tmp_path)

        with funcs.update_repository(target_repo.url) as repo_path:
            (repo_path / new_file_name).write_text(new_file_name)

        # act
        funcs.run_repobee(
            f"repos clone -a {const.TEMPLATE_REPOS_ARG} "
            f"--update-local "
            f"--base-url {platform_url}",
            workdir=tmp_path,
        )

        # assert
        local_repo_path = list(tmp_path.rglob(target_repo.name))[0]
        assert local_repo_path.parent.parent == tmp_path
        local_new_file = local_repo_path / new_file_name
        assert local_new_file.is_file()
        assert local_new_file.read_text() == new_file_name
Ejemplo n.º 9
0
    def test_setup_multiple_template_repos(self, platform_dir, platform_url):
        funcs.run_repobee(f"repos setup -a {TEMPLATE_REPOS_ARG} "
                          f"--base-url {platform_url}")

        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             TEMPLATE_REPO_NAMES,
                                             funcs.get_repos(platform_url))
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
0
    def test_squashed_student_repos_contain_only_squash_commit(
            self, platform_url, tmp_path):
        """When using the squash plugin, student repos should initially only
        contain the squash commit.
        """
        assignment_name = "repo-with-multiple-commits"
        repo_path = tmp_path / assignment_name
        self._setup_local_repo_with_multiple_commits(repo_path)

        squash_message = "This is a strange commit message"
        funcs.run_repobee(
            [
                *plug.cli.CoreCommand.repos.setup.as_name_tuple(),
                "--assignments",
                assignment_name,
                "--allow-local-templates",
                "--base-url",
                platform_url,
                "--squash-message",
                squash_message,
            ],
            plugins=[_repobee.ext.squash],
            workdir=tmp_path,
        )

        repos = funcs.get_repos(platform_url)
        assert repos
        for repo in repos:
            self._assert_single_commit_with_message(repo, squash_message)
Ejemplo n.º 12
0
    def test_setup_with_local_repos(self, platform_url, tmp_path):
        """Test running the setup command with the names of local
        repositories. That is to say, repos that are not in the
        template organization.
        """
        # arrange
        template_repo_hashes = {}

        task_34 = tmp_path / "task-34"
        task_55 = tmp_path / "task-55"
        template_repo_hashes[task_34.name] = create_local_repo(
            task_34, [("somefile.txt", "This is task 34!")])
        template_repo_hashes[task_55.name] = create_local_repo(
            task_55, [("hello.py", "print('hello, world!')")])

        # act
        funcs.run_repobee(
            f"repos setup -a {task_55.name} {task_34.name} "
            f"--base-url {platform_url} "
            "--allow-local-templates",
            workdir=tmp_path,
        )

        # assert
        repo_dict = {
            repo.name: repo.path
            for repo in funcs.get_repos(platform_url)
        }

        _assert_repos_match_templates(
            STUDENT_TEAMS,
            [task_34.name, task_55.name],
            template_repo_hashes,
            repo_dict,
        )
Ejemplo n.º 13
0
    def test_use_non_standard_repo_names(self, platform_url):
        """Test setting up repos with non-standard repo names using an
        implementation of the ``generate_repo_name`` hook.
        """
        def generate_repo_name(team_name, assignment_name):
            return f"{assignment_name}-BONKERS-{team_name}"

        expected_repo_names = [
            generate_repo_name(team, assignment_name)
            for team, assignment_name in itertools.product(
                const.STUDENT_TEAMS, const.TEMPLATE_REPO_NAMES)
        ]

        class StrangeNamingConvention(plug.Plugin):
            def generate_repo_name(self, team_name, assignment_name):
                return generate_repo_name(team_name, assignment_name)

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

        actual_repo_names = [
            repo.name for repo in funcs.get_repos(platform_url)
        ]
        assert sorted(actual_repo_names) == sorted(expected_repo_names)
Ejemplo n.º 14
0
    def test_update_local_stashes_local_changes(self, platform_url,
                                                with_student_repos, tmp_path):
        """Test that updating local repositories with unstaged changes causes
        the changes to be stashed, and the update to proceed.
        """
        new_file_name = "suspicious_file.txt"
        target_repo = funcs.get_repos(platform_url)[-1]
        self._clone_all_student_repos(platform_url, tmp_path)

        # update remote repo
        with funcs.update_repository(target_repo.url) as repo_path:
            (repo_path / new_file_name).write_text(new_file_name)
        # update local repo
        local_repo_path = list(tmp_path.rglob(target_repo.name))[0]
        next(file for file in local_repo_path.iterdir()
             if file.is_file()).write_text("this is an update!")

        # act
        funcs.run_repobee(
            f"repos clone -a {const.TEMPLATE_REPOS_ARG} "
            f"--update-local "
            f"--base-url {platform_url}",
            workdir=tmp_path,
        )

        # assert
        assert local_repo_path.parent.parent == tmp_path
        local_new_file = local_repo_path / new_file_name
        assert local_new_file.is_file()
        assert local_new_file.read_text() == new_file_name
        assert git.Repo(local_repo_path).git.stash("list")
Ejemplo n.º 15
0
    def test_use_local_template_with_strangely_named_default_branch(
            self, platform_url, tmp_path):
        """Test setting up student repos with a template repo that has a
        non-standard default branch name. The student repos should get
        the same default branch.
        """
        strange_branch_name = "definitelynotmaster"
        task_99 = tmp_path / "task-99"
        create_local_repo(
            task_99,
            [("README.md", "Read me plz.")],
            default_branch=strange_branch_name,
        )

        funcs.run_repobee(
            f"repos setup -a {task_99.name} "
            f"--base-url {platform_url} "
            "--allow-local-templates",
            workdir=tmp_path,
        )

        repo = git.Repo(funcs.get_repos(platform_url)[0].path)

        assert len(repo.branches) == 1
        assert repo.branches[0].name == strange_branch_name
Ejemplo n.º 16
0
    def test_does_not_push_to_existing_repos(self, platform_url,
                                             with_student_repos, capsys,
                                             tmp_path):
        """This command should not push to existing repos, that's for the
        ``update`` command to do.
        """
        # arrange
        task = tmp_path / TEMPLATE_REPO_NAMES[0]
        create_local_repo(task, [("best/file/ever.txt", "content")])

        # act
        # this push would fail if it was attempted, as the repo
        # content of the local template does not match that of
        # the remote template
        funcs.run_repobee(
            f"repos setup -a {TEMPLATE_REPOS_ARG} "
            f"--base-url {platform_url} "
            "--allow-local-templates",
            workdir=tmp_path,
        )

        # nothing should have changed, and there should be no errors
        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             TEMPLATE_REPO_NAMES,
                                             funcs.get_repos(platform_url))
        assert "[ERROR]" not in capsys.readouterr().out
Ejemplo n.º 17
0
    def test_setup_single_template_repo(self, platform_dir, platform_url):
        template_repo_name = TEMPLATE_REPO_NAMES[0]
        funcs.run_repobee(f"repos setup -a {template_repo_name} "
                          f"--base-url {platform_url}")

        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             [template_repo_name],
                                             funcs.get_repos(platform_url))
Ejemplo n.º 18
0
    def test_setup_multiple_template_repos_twice(self, platform_dir,
                                                 platform_url):
        """Running setup command twice should have the same effect as running
        it once.
        """
        for _ in range(2):
            funcs.run_repobee(f"repos setup -a {TEMPLATE_REPOS_ARG} "
                              f"--base-url {platform_url} ")

        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             TEMPLATE_REPO_NAMES,
                                             funcs.get_repos(platform_url))
Ejemplo n.º 19
0
    def test_setup_multiple_template_repos_quietly(self, platform_dir,
                                                   platform_url, capsys):
        """Run with `-q` and there should be no output."""
        funcs.run_repobee(f"repos setup -a {TEMPLATE_REPOS_ARG} "
                          f"--base-url {platform_url} "
                          "-q")

        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             TEMPLATE_REPO_NAMES,
                                             funcs.get_repos(platform_url))
        out_err = capsys.readouterr()
        assert not out_err.out.strip()
        assert not out_err.err.strip()
Ejemplo n.º 20
0
    def test_use_strange_default_branch_name(self, platform_url, tmp_path):
        strange_branch_name = "definitelynotmaster"
        task_99 = tmp_path / "task-99"
        create_local_repo(
            task_99,
            [("README.md", "Read me plz.")],
            default_branch=strange_branch_name,
        )

        funcs.run_repobee(
            f"repos migrate -a {task_99.name} "
            f"--base-url {platform_url} "
            "--allow-local-templates",
            workdir=tmp_path,
        )

        platform_repos = funcs.get_repos(platform_url)
        assert len(platform_repos) == 1
        repo = git.Repo(funcs.get_repos(platform_url)[0].path)

        assert len(repo.branches) == 1
        assert repo.branches[0].name == strange_branch_name
Ejemplo n.º 21
0
    def test_does_not_create_repos(self, platform_url):
        """This command should only update existing repos, it's not allowed to
        create repos.
        """
        # arrange, must create the student teams
        funcs.run_repobee(f"teams create --base-url {platform_url}")

        # act
        funcs.run_repobee(
            f"repos update -a {TEMPLATE_REPOS_ARG} --base-url {platform_url}")

        # assert
        assert not funcs.get_repos(platform_url)
Ejemplo n.º 22
0
def test_post_setup_adds_student_repos_to_teacher_team(platform_url):
    """The post_setup hook should add freshly created student repos to the
    teachers team.
    """
    funcs.run_repobee(
        f"repos setup -a {const.TEMPLATE_REPOS_ARG} "
        f"--base-url {platform_url}",
        plugins=[tamanager],
    )

    teachers_team = get_teachers_team(platform_url)
    num_expected_repos = len(const.STUDENT_TEAMS) * len(
        const.TEMPLATE_REPO_NAMES)
    assert len(teachers_team.repos) == num_expected_repos
    expected_repo_names = [r.name for r in funcs.get_repos(platform_url)]
    actual_repo_names = [r.name for r in teachers_team.repos]
    assert sorted(expected_repo_names) == sorted(actual_repo_names)
Ejemplo n.º 23
0
    def test_squashed_student_repos_have_correct_content(self, platform_url):
        """When using the squash plugin, student repos should initially only
        contain the squash commit.
        """
        funcs.run_repobee(
            [
                *plug.cli.CoreCommand.repos.setup.as_name_tuple(),
                "--assignments",
                *TEMPLATE_REPO_NAMES,
                "--base-url",
                platform_url,
            ],
            plugins=[_repobee.ext.squash],
        )

        assert_student_repos_match_templates(STUDENT_TEAMS,
                                             TEMPLATE_REPO_NAMES,
                                             funcs.get_repos(platform_url))
Ejemplo n.º 24
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]
Ejemplo n.º 25
0
def test_add_teachers_twice(platform_url):
    """The effect of running add-teachers once or twice should be identical."""
    # arrange
    teachers = ["gork", "mork", "slanesh"]
    setup_student_repos_and_user_accounts(teachers, platform_url)

    # act
    for _ in range(2):
        funcs.run_repobee(
            f"teams add-teachers --teachers {' '.join(teachers)} "
            f"--base-url {platform_url}",
            plugins=[tamanager],
        )

    # assert
    teachers_team = get_teachers_team(platform_url)
    num_expected_repos = len(const.STUDENT_TEAMS) * len(
        const.TEMPLATE_REPO_NAMES)
    assert len(teachers_team.repos) == num_expected_repos
    expected_repo_names = [r.name for r in funcs.get_repos(platform_url)]
    actual_repo_names = [r.name for r in teachers_team.repos]
    assert sorted(expected_repo_names) == sorted(actual_repo_names)
    assert sorted([m.username
                   for m in teachers_team.members]) == sorted(teachers)