def test_charm_parameters_requirement(config): """The --requirement option implies a set of validations.""" cmd = PackCommand("group", config) parser = ArgumentParser() cmd.fill_parser(parser) (action,) = [action for action in parser._actions if action.dest == "requirement"] assert action.type is useful_filepath
def test_charm_parameters_entrypoint(config): """The --entrypoint option implies a set of validations.""" cmd = PackCommand("group", config) parser = ArgumentParser() cmd.fill_parser(parser) (action,) = [action for action in parser._actions if action.dest == "entrypoint"] assert isinstance(action.type, SingleOptionEnsurer) assert action.type.converter is useful_filepath
def test_resolve_bundle_type(config): """The config indicates the project is a bundle.""" config.set(type="bundle") cmd = PackCommand("group", config) with patch.object(cmd, "_pack_bundle") as mock: cmd.run(noargs) mock.assert_called_with()
def test_resolve_no_config_packs_charm(config): """There is no config, so it's decided to pack a charm.""" config.set(project=Project(config_provided=False)) cmd = PackCommand("group", config) with patch.object(cmd, "_pack_charm") as mock: cmd.run(noargs) mock.assert_called_with(noargs)
def test_resolve_no_config_packs_charm(config, tmp_path): """There is no config, so it's decided to pack a charm.""" config.set(project=Project( config_provided=False, dirpath=tmp_path, started_at=datetime.datetime.utcnow(), )) cmd = PackCommand(config) with patch.object(cmd, "_pack_charm") as mock: cmd.run(noargs) mock.assert_called_with(noargs)
def test_charm_parameters_validator(config, tmp_path): """Check that build.Builder is properly called.""" args = Namespace( destructive_mode=True, requirement="test-reqs", entrypoint="test-epoint", bases_index=[], force=True, ) config.set( type="charm", project=Project(dirpath=tmp_path, started_at=datetime.datetime.utcnow()), ) with patch("charmcraft.commands.build.Validator", autospec=True) as validator_class_mock: validator_class_mock.return_value = validator_instance_mock = MagicMock() with patch("charmcraft.commands.build.Builder"): PackCommand("group", config).run(args) validator_instance_mock.process.assert_called_with( Namespace( **{ "destructive_mode": True, "from": tmp_path, "requirement": "test-reqs", "entrypoint": "test-epoint", "bases_indices": [], "force": True, } ) )
def test_missing_bundle_file(tmp_path, config): """Can not build a bundle without bundle.yaml.""" # build without a bundle.yaml! with pytest.raises(CommandError) as cm: PackCommand('group', config).run(noargs) assert str(cm.value) == ( "Missing or invalid main bundle file: '{}'.".format(tmp_path / 'bundle.yaml'))
def test_bundle_simple_succesful_build(tmp_path, emitter, bundle_yaml, bundle_config): """A simple happy story.""" # mandatory files (other thant the automatically provided manifest) content = bundle_yaml(name="testbundle") bundle_config.set(type="bundle") (tmp_path / "README.md").write_text("test readme") # build! PackCommand(bundle_config).run(noargs) # check zipname = tmp_path / "testbundle.zip" zf = zipfile.ZipFile(zipname) assert "charmcraft.yaml" not in [x.filename for x in zf.infolist()] assert zf.read("bundle.yaml") == content.encode("ascii") assert zf.read("README.md") == b"test readme" expected = "Created '{}'.".format(zipname) emitter.assert_message(expected) # check the manifest is present and with particular values that depend on given info manifest = yaml.safe_load(zf.read("manifest.yaml")) assert manifest[ "charmcraft-started-at"] == bundle_config.project.started_at.isoformat( ) + "Z" # verify that the manifest was not leftover in user's project assert not (tmp_path / "manifest.yaml").exists()
def test_prime_extra_globstar(tmp_path, bundle_yaml, bundle_config): """Double star means whatever directories are in the path.""" bundle_yaml(name="testbundle") bundle_config.set(prime=["lib/**/*"]) srcpaths = ( ("lib/foo/f1.txt", True), ("lib/foo/deep/fx.txt", True), ("lib/bar/f2.txt", True), ("lib/f3.txt", True), ("extra/lib/f.txt", False), ("libs/fs.txt", False), ) for srcpath, expected in srcpaths: testfile = tmp_path / pathlib.Path(srcpath) testfile.parent.mkdir(parents=True, exist_ok=True) testfile.touch() with patch.object(pack, "MANDATORY_FILES", []): PackCommand(bundle_config).run(noargs) zf = zipfile.ZipFile(tmp_path / "testbundle.zip") zipped_files = [x.filename for x in zf.infolist()] for srcpath, expected in srcpaths: assert (srcpath in zipped_files) == expected
def test_resolve_bundle_with_requirement(config): """The requirement option is not valid when packing a bundle.""" config.set(type="bundle") args = Namespace(requirement="reqs.txt", entrypoint=None) with pytest.raises(CommandError) as cm: PackCommand("group", config).run(args) assert str(cm.value) == "The -r/--requirement option is valid only when packing a charm"
def test_bundle_missing_other_mandatory_file(tmp_path, bundle_config, bundle_yaml): """Can not build a bundle without any of the mandatory files.""" bundle_yaml(name="testbundle") bundle_config.set(type="bundle") # build without a README! with pytest.raises(CommandError) as cm: PackCommand("group", bundle_config).run(noargs) assert str(cm.value) == "Missing mandatory file: {!r}.".format(str(tmp_path / "README.md"))
def test_missing_bundle_file(tmp_path, charmcraft_yaml): """Can not build a bundle without bundle.yaml.""" # build without a bundle.yaml! args = Namespace(from_dir=tmp_path) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str( cm.value) == ("Missing or invalid main bundle file: '{}'.".format( tmp_path / 'bundle.yaml'))
def test_specified_directory_not_found(tmp_path): """The specified directory is not there.""" not_there = tmp_path / 'not there' args = Namespace(from_dir=not_there) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str( cm.value) == "Bundle project directory was not found: '{}'.".format( not_there)
def test_bundle_shell_after(tmp_path, bundle_yaml, bundle_config, mock_parts, mock_launch_shell): bundle_yaml(name="testbundle") bundle_config.set(type="bundle") (tmp_path / "README.md").write_text("test readme") PackCommand(bundle_config).run(get_namespace(shell_after=True)) assert mock_launch_shell.mock_calls == [mock.call()]
def test_resolve_bundle_with_entrypoint(config): """The entrypoint option is not valid when packing a bundle.""" config.set(type="bundle") args = Namespace(requirement=None, entrypoint="mycharm.py") with pytest.raises(CommandError) as cm: PackCommand(config).run(args) assert str( cm.value) == "The -e/--entry option is valid only when packing a charm"
def test_specified_directory_not_a_directory(tmp_path): """The specified directory is not really a directory.""" somefile = tmp_path / 'somefile' somefile.touch() args = Namespace(from_dir=somefile) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str(cm.value) == ( "Bundle project directory is not a directory: '{}'.".format(somefile))
def test_bad_type_in_charmcraft(bundle_yaml, config): """The charmcraft.yaml file must have a proper type field.""" bundle_yaml(name='testbundle') config.set(type='charm') # build! with pytest.raises(CommandError) as cm: PackCommand('group', config).run(noargs) assert str(cm.value) == ( "Bad config: 'type' field in charmcraft.yaml must be 'bundle' for this command.")
def test_missing_name_in_bundle(tmp_path, bundle_yaml, config): """Can not build a bundle without name.""" config.set(type='bundle') # build! with pytest.raises(CommandError) as cm: PackCommand('group', config).run(noargs) assert str(cm.value) == ( "Invalid bundle config; missing a 'name' field indicating the bundle's name in file '{}'." .format(tmp_path / 'bundle.yaml'))
def test_charm_builder_infrastructure_called(config): """Check that build.Builder is properly called.""" config.set(type="charm") with patch("charmcraft.commands.build.Validator", autospec=True) as validator_mock: validator_mock(config).process.return_value = "processed args" with patch("charmcraft.commands.build.Builder") as builder_class_mock: builder_class_mock.return_value = builder_instance_mock = MagicMock() PackCommand("group", config).run(noargs) builder_class_mock.assert_called_with("processed args", config) builder_instance_mock.run.assert_called_with([], destructive_mode=False)
def test_bundle_missing_name_in_bundle(tmp_path, bundle_yaml, bundle_config): """Can not build a bundle without name.""" bundle_config.set(type="bundle") # build! with pytest.raises(CraftError) as cm: PackCommand(bundle_config).run(noargs) assert str(cm.value) == ( "Invalid bundle config; " "missing a 'name' field indicating the bundle's name in file '{}'.". format(tmp_path / "bundle.yaml"))
def test_missing_charmcraft_file(tmp_path, bundle_yaml): """Can not build a bundle without charmcraft.yaml.""" bundle_yaml(name='testbundle') # build without a charmcraft.yaml! args = Namespace(from_dir=tmp_path) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str( cm.value) == ("Missing or invalid charmcraft file: '{}'.".format( tmp_path / 'charmcraft.yaml'))
def test_missing_name_in_bundle(tmp_path, bundle_yaml, charmcraft_yaml): """Can not build a bundle without name.""" charmcraft_yaml(type='bundle') # build! args = Namespace(from_dir=tmp_path) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str(cm.value) == ( "Invalid bundle config; missing a 'name' field indicating the bundle's name in file '{}'." .format(tmp_path / 'bundle.yaml'))
def test_missing_type_in_charmcraft(tmp_path, bundle_yaml, charmcraft_yaml): """The charmcraft.yaml file must have a proper type field.""" bundle_yaml(name='testbundle') # build! args = Namespace(from_dir=tmp_path) with pytest.raises(CommandError) as cm: PackCommand('group').run(args) assert str(cm.value) == ( "Invalid charmcraft config; 'type' must be 'bundle' in file '{}'.". format(tmp_path / 'charmcraft.yaml'))
def test_bundle_debug_with_error(tmp_path, bundle_yaml, bundle_config, mock_parts, mock_launch_shell): mock_parts.PartsLifecycle.return_value.run.side_effect = CraftError("fail") bundle_yaml(name="testbundle") bundle_config.set(type="bundle") (tmp_path / "README.md").write_text("test readme") with pytest.raises(CraftError): PackCommand(bundle_config).run(get_namespace(debug=True)) assert mock_launch_shell.mock_calls == [mock.call()]
def test_prime_extra_wildcards_not_found(tmp_path, bundle_yaml, bundle_config): """Use wildcards to specify several files but nothing found.""" bundle_yaml(name="testbundle") bundle_config.set(prime=["*.txt"]) # non-existent files are not included if using a wildcard with patch.object(pack, "MANDATORY_FILES", []): PackCommand(bundle_config).run(noargs) zf = zipfile.ZipFile(tmp_path / "testbundle.zip") zipped_files = [x.filename for x in zf.infolist()] assert zipped_files == ["manifest.yaml"]
def test_prime_extra_missing(tmp_path, bundle_yaml, bundle_config): """Extra files were indicated but not found.""" bundle_yaml(name="testbundle") bundle_config.set(prime=["f2.txt", "f1.txt"]) testfile1 = tmp_path / "f1.txt" testfile1.touch() with patch.object(pack, "MANDATORY_FILES", []): with pytest.raises(CommandError) as err: PackCommand(bundle_config).run(noargs) assert str(err.value) == ( "Parts processing error: Failed to copy '{}/build/stage/f2.txt': " "no such file or directory.".format(tmp_path))
def test_prime_extra_long_path(tmp_path, bundle_yaml, bundle_config): """An extra file can be deep in directories.""" bundle_yaml(name="testbundle") bundle_config.set(prime=["foo/bar/baz/extra.txt"]) testfile = tmp_path / "foo" / "bar" / "baz" / "extra.txt" testfile.parent.mkdir(parents=True) testfile.touch() with patch.object(pack, "MANDATORY_FILES", []): PackCommand(bundle_config).run(noargs) zf = zipfile.ZipFile(tmp_path / "testbundle.zip") zipped_files = [x.filename for x in zf.infolist()] assert "foo/bar/baz/extra.txt" in zipped_files
def test_prime_extra_ok(tmp_path, bundle_yaml, bundle_config): """Extra files were indicated ok.""" bundle_yaml(name="testbundle") bundle_config.set(prime=["f2.txt", "f1.txt"]) testfile1 = tmp_path / "f1.txt" testfile1.touch() testfile2 = tmp_path / "f2.txt" testfile2.touch() with patch.object(pack, "MANDATORY_FILES", []): PackCommand(bundle_config).run(noargs) zf = zipfile.ZipFile(tmp_path / "testbundle.zip") zipped_files = [x.filename for x in zf.infolist()] assert "f1.txt" in zipped_files assert "f2.txt" in zipped_files