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
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
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
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)
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)
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
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)
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
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))
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)
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)
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, )
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)
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")
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
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
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))
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))
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()
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
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)
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)
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))
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]
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)