def test_create_new_category(self): """Test that command can be added to a new category.""" category = plug.cli.category("greetings", action_names=["hello"]) class Hello(plug.Plugin, plug.cli.Command): __settings__ = plug.cli.command_settings(action=category.hello) name = plug.cli.positional() age = plug.cli.positional(converter=int) def command(self): return plug.Result( name=self.__plugin_name__, msg="Nice!", status=plug.Status.SUCCESS, data={ "name": self.name, "age": self.age }, ) name = "Bob" age = 24 results_mapping = repobee.run(f"greetings hello {name} {age}".split(), plugins=[Hello]) print(results_mapping) _, results = list(results_mapping.items())[0] result, *_ = results assert result.data["name"] == name assert result.data["age"] == age
def run_repobee(cmd, workdir=pathlib.Path(".")): result = repobee.run( cmd, plugins=[sanitizer.SanitizeFile, sanitizer.SanitizeRepo], workdir=workdir, ) return list(result.values())[0][0]
def test_raises_on_non_interactive_install_of_non_existing_version(self): """An error should be raised if one tries to install a version that does not exist, but the plugin does. """ plugin_name = "junit4" plugin_version = "v0.32.0" cmd = [ *pluginmanager.plugin_category.install.as_name_tuple(), "--plugin-spec", f"{plugin_name}{pluginmanager.PLUGIN_SPEC_SEP}{plugin_version}", ] with pytest.raises(plug.PlugError) as exc_info: repobee.run(cmd) assert (f"plugin '{plugin_name}' has no version '{plugin_version}'" in str(exc_info.value))
def test_raises_on_malformed_plugin_spec(self): """An error should be raised if a plugin spec is malformed.""" malformed_spec = pluginmanager.PLUGIN_SPEC_SEP.join( ["too", "many", "parts"] ) cmd = [ *pluginmanager.plugin_category.install.as_name_tuple(), "--plugin-spec", malformed_spec, ] with pytest.raises(plug.PlugError) as exc_info: repobee.run(cmd) assert f"malformed plugin spec '{malformed_spec}'" in str( exc_info.value )
def with_student_repos(): command = re.sub( r"\s+", " ", f""" repos setup --bu https://localhost:3000/api/v1 --token {giteamanager.TEACHER_TOKEN} --user {giteamanager.TEACHER_USER} --org-name {giteamanager.TARGET_ORG_NAME} --template-org-name {giteamanager.TEMPLATE_ORG_NAME} --students {' '.join([t.members[0] for t in giteamanager.STUDENT_TEAMS])} --assignments {' '.join(template_helpers.TEMPLATE_REPO_NAMES)} --tb """, ) repobee.run(shlex.split(command), plugins=[gitea])
def test_non_interactive_deactivate_of_builtin_plugin(self, install_dir): # arrange plugin_name = "ghclassroom" cmd = [ *pluginmanager.plugin_category.activate.as_name_tuple(), "--plugin-name", plugin_name, ] repobee.run(cmd) # act repobee.run(cmd) # assert assert plugin_name not in disthelpers.get_active_plugins( install_dir / "installed_plugins.json" )
def test_add_required_option_to_config_show(self, capsys, tmpdir, config_file): """Tests adding a required option to ``config show``.""" class ConfigShowExt(plug.Plugin, plug.cli.CommandExtension): __settings__ = plug.cli.command_extension_settings( actions=[plug.cli.CoreCommand.config.show]) silly_new_option = plug.cli.option(help="your name", required=True) with pytest.raises(SystemExit): repobee.run( "config show".split(), config_file=config_file, plugins=[ConfigShowExt], ) assert ("the following arguments are required: --silly-new-option" in capsys.readouterr().err)
def test_install_local_plugin_package(self): plugin_version = "1.0.0" with tempfile.TemporaryDirectory() as tmpdir: workdir = pathlib.Path(tmpdir) junit4_local = workdir / "repobee-junit4" repo = git.Repo.clone_from( "https://github.com/repobee/repobee-junit4", to_path=junit4_local, ) repo.git.checkout(f"v{plugin_version}") repobee.run(shlex.split(f"plugin install --local {junit4_local}")) install_info = disthelpers.get_installed_plugins()["junit4"] assert install_info["version"] == "local" assert install_info["path"] == str(junit4_local) assert get_pkg_version("repobee-junit4") == plugin_version
def test_truncates_urls_to_fit_terminal_width(self, capsys, mocker): """When the terminal is too narrow, the URLs in the plugins table are the first to be truncated. """ cols = 120 # URLs need ~140 cols to fit mocker.patch( "os.get_terminal_size", autospec=True, return_value=collections.namedtuple("TermSize", "columns lines")(columns=cols, lines=100), ) repobee.run("plugin list".split()) out_err = capsys.readouterr() assert "truncating: 'URL'" in out_err.err assert "https://github.com" not in out_err.out
def test_install_local_plugin_file(self, capsys, tmp_path): plugin_content = """ import repobee_plug as plug class Hello(plug.Plugin, plug.cli.Command): def command(self): return plug.Result( name='hello', status=plug.Status.SUCCESS, msg='Best message' ) """ hello_py = tmp_path / "hello.py" hello_py.write_text(plugin_content, encoding="utf8") repobee.run(shlex.split(f"plugin install --local {hello_py}")) install_info = disthelpers.get_installed_plugins()[str(hello_py)] assert install_info["version"] == "local" assert install_info["path"] == str(hello_py)
def test_config_parent_path_is_relative_to_config_path( self, tmp_path_factory): """Tests that the parent path in a config file is relative to that that configs own path (as opposed to the current working directory). """ # arrange root_dir = tmp_path_factory.mktemp("configs") parent_config_dir = root_dir / "parent_config_dir" child_config_dir = root_dir / "child_config_dir" parent_config_dir.mkdir() child_config_dir.mkdir() key = "darth" value = "vader" parent_config = plug.Config(parent_config_dir / "base-config.ini") parent_config[plug.Config.CORE_SECTION_NAME][key] = value parent_config.store() child_config = plug.Config(child_config_dir / "config.ini") child_config[plug.Config.CORE_SECTION_NAME][ plug.Config.PARENT_CONFIG_KEY] = str( pathlib.Path("..") / parent_config.path.relative_to(root_dir)) child_config.store() fetched_value = None class HandleConfig(plug.Plugin): def handle_config(self, config: plug.Config) -> None: nonlocal fetched_value fetched_value = config.get(plug.Config.CORE_SECTION_NAME, key) # act repobee.run( list(plug.cli.CoreCommand.config.show.as_name_tuple()), config_file=child_config.path, plugins=[HandleConfig], ) # assert assert fetched_value == value
def test_cannot_downgrade_repobee_version(self, mocker): """Test that installing a version of a plugin that requires an older version of RepoBee does fails. In other words, the plugin should not be installed and RepoBee should not be downgraded. """ current_version = str(version.Version(_repobee.__version__)) if get_pkg_version("repobee") != current_version: pytest.skip("unreleased version, can't run downgrade test") # this version of sanitizer requires repobee==3.0.0-alpha.5 sanitizer_version = "2110de7952a75c03f4d33e8f2ada78e8aca29c57" mocker.patch( "bullet.Bullet.launch", side_effect=["sanitizer", sanitizer_version], ) repobee_initial_version = get_pkg_version("repobee") with pytest.raises(disthelpers.DependencyResolutionError): repobee.run("plugin install".split()) assert get_pkg_version("repobee") == repobee_initial_version
def test_handle_config_hook_recieves_config_with_inherited_properties( self, tmp_path_factory): first_tmpdir = tmp_path_factory.mktemp("configs") second_tmpdir = tmp_path_factory.mktemp("other-configs") section_name = "repobee" parent_key = "template_org_name" parent_value = "some-value" child_key = "org_name" child_value = "something" parent_config = plug.Config(second_tmpdir / "base-config.ini") parent_config[section_name][parent_key] = parent_value parent_config.store() child_config = plug.Config(first_tmpdir / "config.ini") child_config[section_name][child_key] = child_value child_config.parent = parent_config child_config.store() fetched_child_value = None fetched_parent_value = None class HandleConfig(plug.Plugin): def handle_config(self, config: plug.Config) -> None: nonlocal fetched_child_value, fetched_parent_value fetched_child_value = config.get(section_name, child_key) fetched_parent_value = config.get(section_name, parent_key) repobee.run( list(plug.cli.CoreCommand.config.show.as_name_tuple()), config_file=child_config.path, plugins=[HandleConfig], ) assert fetched_child_value == child_value assert fetched_parent_value == parent_value
def test_auto_updates_pip(self): """Installing a plugin should automatically update pip if it's out-of-date. """ # arrange old_pip_version = "20.0.1" assert ( subprocess.run( [ disthelpers.get_pip_path(), "install", "-U", f"pip=={old_pip_version}", ] ).returncode == 0 ) assert version.Version(get_pkg_version("pip")) == version.Version( old_pip_version ) plugin_name = "junit4" plugin_version = "v1.0.0" cmd = [ *pluginmanager.plugin_category.install.as_name_tuple(), "--plugin-spec", f"{plugin_name}{pluginmanager.PLUGIN_SPEC_SEP}{plugin_version}", ] # act repobee.run(cmd) # assert assert version.Version(get_pkg_version("pip")) > version.Version( old_pip_version )
def test_install_plugin_version_incompatible_with_repobee_version_error_message( self, tmp_path ): old_repobee_version = "3.4.0" current_repobee_version = get_pkg_version("repobee") plugin_version = "1.0.0" plugin_dir = tmp_path / "repobee-bogus" plugin_dir.mkdir() self._setup_bogus_plugin( old_repobee_version, plugin_version, plugin_dir ) with pytest.raises(plug.PlugError) as exc_info: repobee.run(shlex.split(f"plugin install --local {plugin_dir}")) assert ( f"Selected plugin is incompatible with " f"RepoBee v{current_repobee_version}" in str(exc_info.value) ) assert ( "Try upgrading RepoBee and then install the plugin again" in str(exc_info.value) )
def run_repobee(cmd: Union[str, List[str]], **kwargs) -> Mapping[str, List[plug.Result]]: """Helper function to call :py:class:`repobee.run` when using the :py:class:`fakeapi.FakeAPI` platform API. This function will by default use a config file that sets appropriate values for ``students_file``, ``user``, ``org_name`` and ``template_org_name`` for use with the :py:class:`~fakeapi.FakeAPI` platform API. If you wish to use a different config, simply pass ``config_file="/path/to/your/config"`` to the function, or ``config_file=""`` to not use a config file at all. The :py:class:`~fakeapi.FakeAPI` plugin is always loaded last, so it is the not possible to use another platform API with this function. If you wish to do so, you should use :py:class`repobee.run` directly instead. Args: cmd: A string or list of strings with a RepoBee command. kwargs: Keyword arguments for :py:func:`repobee.run`. Returns: The results mapping returned by :py:func:`repobee.run` """ cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd kwargs = dict(kwargs) # copy to not mutate input plugins = (kwargs.get("plugins") or []) + [localapi] kwargs["plugins"] = plugins students_file = (pathlib.Path(__file__).parent / "resources" / "students.txt") with tempfile.NamedTemporaryFile() as tmp: config_file = pathlib.Path(tmp.name) config_file.write_text(f"""[repobee] students_file = {students_file} org_name = {const.TARGET_ORG_NAME} user = {const.TEACHER} template_org_name = {const.TEMPLATE_ORG_NAME} token = {const.TOKEN} """) kwargs.setdefault("config_file", config_file) return repobee.run(cmd, **kwargs)
def install_plugin(name: str, version: str) -> None: # arrange with mock.patch("bullet.Bullet.launch", side_effect=[name, version]): repobee.run("plugin install".split()) assert get_pkg_version(f"repobee-{name}")