import six import os import yaml from testtools import TestCase from mock import ( patch, MagicMock, call, ) from charmhelpers import fetch if six.PY3: from urllib.parse import urlparse builtin_open = 'builtins.open' else: from urlparse import urlparse builtin_open = '__builtin__.open' FAKE_APT_CACHE = { # an installed package 'vim': { 'current_ver': '2:7.3.547-6ubuntu5' }, # a uninstalled installation candidate 'emacs': { } } def fake_apt_cache(in_memory=True, progress=None): def _get(package): pkg = MagicMock() if package not in FAKE_APT_CACHE: raise KeyError pkg.name = package if 'current_ver' in FAKE_APT_CACHE[package]: pkg.current_ver.ver_str = FAKE_APT_CACHE[package]['current_ver'] else: pkg.current_ver = None return pkg cache = MagicMock() cache.__getitem__.side_effect = _get return cache def getenv(update=None): # return a copy of os.environ with update applied. # this was necessary because some modules modify os.environment directly copy = os.environ.copy() if update is not None: copy.update(update) return copy class FetchTest(TestCase): @patch('charmhelpers.fetch.log') @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_single_source(self, add_source, config, log): config.side_effect = ['source', 'key'] fetch.configure_sources() add_source.assert_called_with('source', 'key') @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_null_source(self, add_source, config): config.side_effect = [None, None] fetch.configure_sources() self.assertEqual(add_source.call_count, 0) @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_empty_source(self, add_source, config): config.side_effect = ['', ''] fetch.configure_sources() self.assertEqual(add_source.call_count, 0) @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_single_source_no_key(self, add_source, config): config.side_effect = ['source', None] fetch.configure_sources() add_source.assert_called_with('source', None) @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_multiple_sources(self, add_source, config): sources = ["sourcea", "sourceb"] keys = ["keya", None] config.side_effect = [ yaml.dump(sources), yaml.dump(keys) ] fetch.configure_sources() add_source.assert_has_calls([ call('sourcea', 'keya'), call('sourceb', None) ]) @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_missing_keys(self, add_source, config): sources = ["sourcea", "sourceb"] keys = ["keya"] # Second key is missing config.side_effect = [ yaml.dump(sources), yaml.dump(keys) ] self.assertRaises(fetch.SourceConfigError, fetch.configure_sources) @patch.object(fetch, '_fetch_update') @patch.object(fetch, 'config') @patch.object(fetch, 'add_source') def test_configure_sources_update_called_ubuntu(self, add_source, config, update): config.side_effect = ['source', 'key'] fetch.configure_sources(update=True) add_source.assert_called_with('source', 'key') self.assertTrue(update.called) class InstallTest(TestCase): def setUp(self): super(InstallTest, self).setUp() self.valid_urls = ( "http://example.com/foo.tar.gz", "http://example.com/foo.tgz", "http://example.com/foo.tar.bz2", "http://example.com/foo.tbz2", "http://example.com/foo.zip", "http://example.com/foo.zip?bar=baz&x=y#whee", "ftp://example.com/foo.tar.gz", "https://example.com/foo.tgz", "file://example.com/foo.tar.bz2", "bzr+ssh://example.com/branch-name", "bzr+ssh://example.com/branch-name/", "lp:branch-name", "lp:example/branch-name", ) self.invalid_urls = ( "git://example.com/foo.tar.gz", "http://example.com/foo", "http://example.com/foobar=baz&x=y#tar.gz", "http://example.com/foobar?h=baz.zip", "abc:example", "file//example.com/foo.tar.bz2", "garbage", ) @patch('charmhelpers.fetch.log') @patch('charmhelpers.fetch.plugins') def test_installs_remote(self, _plugins, _log): h1 = MagicMock(name="h1") h1.can_handle.return_value = "Nope" h2 = MagicMock(name="h2") h2.can_handle.return_value = True h2.install.side_effect = fetch.UnhandledSource() h3 = MagicMock(name="h3") h3.can_handle.return_value = True h3.install.return_value = "foo" _plugins.return_value = [h1, h2, h3] for url in self.valid_urls: result = fetch.install_remote(url) h1.can_handle.assert_called_with(url) h2.can_handle.assert_called_with(url) h3.can_handle.assert_called_with(url) h1.install.assert_not_called() h2.install.assert_called_with(url) h3.install.assert_called_with(url) self.assertEqual(result, "foo") fetch.install_remote('url', extra_arg=True) h2.install.assert_called_with('url', extra_arg=True) @patch('charmhelpers.fetch.install_remote') @patch('charmhelpers.fetch.config') def test_installs_from_config(self, _config, _instrem): for url in self.valid_urls: _config.return_value = {"foo": url} fetch.install_from_config("foo") _instrem.assert_called_with(url) class PluginTest(TestCase): @patch('charmhelpers.fetch.importlib.import_module') def test_imports_plugins(self, import_): fetch_handlers = ['a.foo', 'b.foo', 'c.foo'] module = MagicMock() import_.return_value = module plugins = fetch.plugins(fetch_handlers) self.assertEqual(len(fetch_handlers), len(plugins)) module.foo.assert_has_calls(([call()] * len(fetch_handlers))) @patch('charmhelpers.fetch.importlib.import_module') def test_imports_plugins_default(self, import_): module = MagicMock() import_.return_value = module plugins = fetch.plugins() self.assertEqual(len(fetch.FETCH_HANDLERS), len(plugins)) for handler in fetch.FETCH_HANDLERS: classname = handler.rsplit('.', 1)[-1] getattr(module, classname).assert_called_with() @patch('charmhelpers.fetch.log') @patch('charmhelpers.fetch.importlib.import_module') def test_skips_and_logs_missing_plugins(self, import_, log_): fetch_handlers = ['a.foo', 'b.foo', 'c.foo'] import_.side_effect = (NotImplementedError, NotImplementedError, MagicMock()) plugins = fetch.plugins(fetch_handlers) self.assertEqual(1, len(plugins)) self.assertEqual(2, log_.call_count) @patch('charmhelpers.fetch.log') @patch.object(fetch.importlib, 'import_module') def test_plugins_are_valid(self, import_module, log_): plugins = fetch.plugins() self.assertEqual(len(fetch.FETCH_HANDLERS), len(plugins)) class BaseFetchHandlerTest(TestCase): def setUp(self): super(BaseFetchHandlerTest, self).setUp() self.test_urls = ( "http://example.com/foo?bar=baz&x=y#blarg", "https://example.com/foo", "ftp://example.com/foo", "file://example.com/foo", "git://github.com/foo/bar", "bzr+ssh://bazaar.launchpad.net/foo/bar", "bzr+http://bazaar.launchpad.net/foo/bar", "garbage", ) self.fh = fetch.BaseFetchHandler() def test_handles_nothing(self): for url in self.test_urls: self.assertNotEqual(self.fh.can_handle(url), True) def test_install_throws_unhandled(self): for url in self.test_urls: self.assertRaises(fetch.UnhandledSource, self.fh.install, url) def test_parses_urls(self): sample_url = "http://example.com/foo?bar=baz&x=y#blarg" p = self.fh.parse_url(sample_url) self.assertEqual(p, urlparse(sample_url)) def test_returns_baseurl(self): sample_url = "http://example.com/foo?bar=baz&x=y#blarg" expected_url = "http://example.com/foo" u = self.fh.base_url(sample_url) self.assertEqual(u, expected_url)