def testLocalPath(self): # 0launch --get-selections Local.xml iface = os.path.join(mydir, "Local.xml") driver = Driver(requirements = Requirements(iface), config = self.config) driver.need_download() assert driver.solver.ready s1 = driver.solver.selections xml = s1.toDOM().toxml("utf-8") # Reload selections and check they're the same root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) local_path = s2.selections[iface].local_path assert os.path.isdir(local_path), local_path assert not s2.selections[iface].digests, s2.selections[iface].digests # Add a newer implementation and try again feed = self.config.iface_cache.get_feed(iface) impl = model.ZeroInstallImplementation(feed, "foo bar=123", local_path = None) impl.version = model.parse_version('1.0') impl.commands["run"] = model.Command(qdom.Element(namespaces.XMLNS_IFACE, 'command', {'path': 'dummy', 'name': 'run'}), None) impl.add_download_source('http://localhost/bar.tgz', 1000, None) feed.implementations = {impl.id: impl} assert driver.need_download() assert driver.solver.ready, driver.solver.get_failure_reason() s1 = driver.solver.selections xml = s1.toDOM().toxml("utf-8") root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) xml = s2.toDOM().toxml("utf-8") qdom.parse(BytesIO(xml)) assert s2.selections[iface].local_path is None assert not s2.selections[iface].digests, s2.selections[iface].digests assert s2.selections[iface].id == 'foo bar=123'
def testLocalPath(self): # 0launch --get-selections Local.xml iface = os.path.join(mydir, "Local.xml") p = policy.Policy(iface, config=self.config) p.need_download() assert p.ready s1 = selections.Selections(p) xml = s1.toDOM().toxml("utf-8") # Reload selections and check they're the same root = qdom.parse(StringIO(xml)) s2 = selections.Selections(root) local_path = s2.selections[iface].local_path assert os.path.isdir(local_path), local_path assert not s2.selections[iface].digests, s2.selections[iface].digests # Add a newer implementation and try again feed = self.config.iface_cache.get_feed(iface) impl = model.ZeroInstallImplementation(feed, "foo bar=123", local_path=None) impl.version = model.parse_version("1.0") impl.commands["run"] = model.Command(qdom.Element(namespaces.XMLNS_IFACE, "command", {"path": "dummy"}), None) impl.add_download_source("http://localhost/bar.tgz", 1000, None) feed.implementations = {impl.id: impl} assert p.need_download() assert p.ready, p.solver.get_failure_reason() s1 = selections.Selections(p) xml = s1.toDOM().toxml("utf-8") root = qdom.parse(StringIO(xml)) s2 = selections.Selections(root) xml = s2.toDOM().toxml("utf-8") qdom.parse(StringIO(xml)) assert s2.selections[iface].local_path is None assert not s2.selections[iface].digests, s2.selections[iface].digests assert s2.selections[iface].id == "foo bar=123"
def get_selections(self, prompt = False): if self._selections: assert not prompt return self._selections selections_file = self.config.get('compile', 'selections') if selections_file: if prompt: raise SafeException("Selections are fixed by %s" % selections_file) stream = file(selections_file) try: self._selections = selections.Selections(qdom.parse(stream)) finally: stream.close() from zeroinstall.injector import handler from zeroinstall.injector.config import load_config if os.isatty(1): h = handler.ConsoleHandler() else: h = handler.Handler() config = load_config(h) blocker = self._selections.download_missing(config) if blocker: print "Waiting for selected implementations to be downloaded..." h.wait_for_blocker(blocker) else: command = install_prog + ['download', '--source', '--xml'] if prompt and '--console' not in install_prog: if os.name == 'nt': command[0] += '-win' command.append('--gui') command.append(self.interface) child = subprocess.Popen(command, stdout = subprocess.PIPE) try: self._selections = selections.Selections(qdom.parse(child.stdout)) finally: if child.wait(): raise SafeException(' '.join(repr(x) for x in command) + " failed (exit code %d)" % child.returncode) self.root_impl = self._selections.selections[self.interface] self.orig_srcdir = os.path.realpath(lookup(self.root_impl)) self.user_srcdir = None if os.path.isdir('src'): self.user_srcdir = os.path.realpath('src') if self.user_srcdir == self.orig_srcdir or \ self.user_srcdir.startswith(os.path.join(self.orig_srcdir, '')) or \ self.orig_srcdir.startswith(os.path.join(self.user_srcdir, '')): info("Ignoring 'src' directory because it coincides with %s", self.orig_srcdir) self.user_srcdir = None return self._selections
def testOverlay(self): for xml, expected in [(b'<overlay/>', '<overlay . on />'), (b'<overlay src="usr"/>', '<overlay usr on />'), (b'<overlay src="package" mount-point="/usr/games"/>', '<overlay package on /usr/games>')]: e = qdom.parse(BytesIO(xml)) ol = model.process_binding(e) self.assertEqual(expected, str(ol)) doc = minidom.parseString('<doc/>') new_xml = ol._toxml(doc, None).toxml(encoding = 'utf-8') new_e = qdom.parse(BytesIO(new_xml)) new_ol = model.process_binding(new_e) self.assertEqual(expected, str(new_ol))
def testOverlay(self): for xml, expected in [('<overlay/>', '<overlay . on />'), ('<overlay src="usr"/>', '<overlay usr on />'), ('<overlay src="package" mount-point="/usr/games"/>', '<overlay package on /usr/games>')]: e = qdom.parse(StringIO(xml)) ol = model.process_binding(e) self.assertEquals(expected, str(ol)) doc = minidom.parseString('<doc/>') new_xml = str(ol._toxml(doc).toxml()) new_e = qdom.parse(StringIO(new_xml)) new_ol = model.process_binding(new_e) self.assertEquals(expected, str(new_ol))
def testOldCommands(self): command_feed = os.path.join(mydir, 'old-selections.xml') with open(command_feed, 'rb') as stream: s1 = selections.Selections(qdom.parse(stream)) self.assertEqual("run", s1.command) self.assertEqual(2, len(s1.commands)) self.assertEqual("bin/java", s1.commands[1].path) xml = s1.toDOM().toxml("utf-8") root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) self.assertEqual("run", s2.command) self.assertEqual(2, len(s2.commands)) self.assertEqual("bin/java", s2.commands[1].path)
def get_selections(self, snapshot_date=None, may_update=False, use_gui=None): """Load the selections. If may_update is True then the returned selections will be cached and available. @param snapshot_date: get a historical snapshot @type snapshot_date: (as returned by L{get_history}) | None @param may_update: whether to check for updates @type may_update: bool @param use_gui: whether to use the GUI for foreground updates @type use_gui: bool | None (never/always/if possible) @return: the selections @rtype: L{selections.Selections}""" if snapshot_date: assert may_update is False, "Can't update a snapshot!" sels_file = os.path.join(self.path, 'selections-' + snapshot_date + '.xml') else: sels_file = os.path.join(self.path, 'selections.xml') try: with open(sels_file, 'rb') as stream: sels = selections.Selections(qdom.parse(stream)) except IOError as ex: if may_update and ex.errno == errno.ENOENT: logger.info("App selections missing: %s", ex) sels = None else: raise if may_update: sels = self._check_for_updates(sels, use_gui) return sels
def testSelectionsWithFeed(self): from zeroinstall.injector import cli root = qdom.parse(file("selections.xml")) sels = selections.Selections(root) with output_suppressed(): self.child = server.handle_requests( "Hello.xml", "6FCF121BE2390E0B.gpg", "/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B", "HelloWorld.tgz", ) sys.stdin = Reply("Y\n") self.config.handler.wait_for_blocker( self.config.fetcher.download_and_import_feed( "http://example.com:8000/Hello.xml", self.config.iface_cache ) ) cli.main(["--download-only", "selections.xml"], config=self.config) path = self.config.stores.lookup_any(sels.selections["http://example.com:8000/Hello.xml"].digests) assert os.path.exists(os.path.join(path, "HelloWorld", "main")) assert sels.download_missing(self.config) is None
def testSelections(self): from zeroinstall.injector import cli root = qdom.parse(file("selections.xml")) sels = selections.Selections(root) class Options: dry_run = False with output_suppressed(): self.child = server.handle_requests( "Hello.xml", "6FCF121BE2390E0B.gpg", "/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B", "HelloWorld.tgz", ) sys.stdin = Reply("Y\n") try: self.config.stores.lookup_any(sels.selections["http://example.com:8000/Hello.xml"].digests) assert False except NotStored: pass cli.main(["--download-only", "selections.xml"]) path = self.config.stores.lookup_any(sels.selections["http://example.com:8000/Hello.xml"].digests) assert os.path.exists(os.path.join(path, "HelloWorld", "main")) assert sels.download_missing(self.config) is None
def update_user_overrides(interface): """Update an interface with user-supplied information. Sets preferred stability and updates extra_feeds. @param interface: the interface object to update @type interface: L{model.Interface} """ user = basedir.load_first_config(config_site, config_prog, 'interfaces', model._pretty_escape(interface.uri)) if user is None: # For files saved by 0launch < 0.49 user = basedir.load_first_config(config_site, config_prog, 'user_overrides', escape(interface.uri)) if not user: return try: root = qdom.parse(file(user)) except Exception as ex: warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex}) raise stability_policy = root.getAttribute('stability-policy') if stability_policy: interface.set_stability_policy(stability_levels[str(stability_policy)]) for item in root.childNodes: if item.uri != XMLNS_IFACE: continue if item.name == 'feed': feed_src = item.getAttribute('src') if not feed_src: raise InvalidInterface(_('Missing "src" attribute in <feed>')) interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
def handle(config, options, args): if len(args) != 1: raise UsageError() app = config.app_mgr.lookup_app(args[0], missing_ok=True) if app is not None: sels = app.get_selections() r = app.get_requirements() if r.extra_restrictions and not options.xml: print("User-provided restrictions in force:") for uri, expr in r.extra_restrictions.items(): print(" {uri}: {expr}".format(uri=uri, expr=expr)) print() elif os.path.exists(args[0]): with open(args[0], 'rb') as stream: sels = selections.Selections(qdom.parse(stream)) else: raise SafeException(_("Neither an app nor a file: '%s'") % args[0]) if options.root_uri: print(sels.interface) elif options.xml: select.show_xml(sels) else: select.show_human(sels, config.stores)
def testSelections(self): from zeroinstall.injector import cli root = qdom.parse(file("selections.xml")) sels = selections.Selections(root) class Options: dry_run = False with output_suppressed(): self.child = server.handle_requests( 'Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz') sys.stdin = Reply("Y\n") try: self.config.stores.lookup_any( sels.selections['http://example.com:8000/Hello.xml']. digests) assert False except NotStored: pass cli.main(['--download-only', 'selections.xml'], config=self.config) path = self.config.stores.lookup_any( sels.selections['http://example.com:8000/Hello.xml'].digests) assert os.path.exists(os.path.join(path, 'HelloWorld', 'main')) assert sels.download_missing(self.config) is None
def load_feed(source, local=False, selections_ok=False): """Load a feed from a local file. @param source: the name of the file to read @type source: str @param local: this is a local feed @type local: bool @param selections_ok: if it turns out to be a local selections document, return that instead @type selections_ok: bool @raise InvalidInterface: if the source's syntax is incorrect @return: the new feed @since: 0.48 @see: L{iface_cache.iface_cache}, which uses this to load the feeds""" try: root = qdom.parse(open(source)) except IOError as ex: if ex.errno == errno.ENOENT and local: raise MissingLocalFeed( _("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case." )) raise InvalidInterface(_("Can't read file"), ex) except Exception as ex: raise InvalidInterface(_("Invalid XML"), ex) if local: if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections': from zeroinstall.injector import selections return selections.Selections(root) local_path = source else: local_path = None feed = ZeroInstallFeed(root, local_path) feed.last_modified = int(os.stat(source).st_mtime) return feed
def build(selections_xml): # Get the chosen versions sels = selections.Selections(qdom.parse(BytesIO(selections_xml))) impl = sels.selections[interface_uri] min_version = impl.attrs.get(XMLNS_0COMPILE + " min-version", our_min_version) # Check the syntax is valid and the version is high enough if model.parse_version(min_version) < model.parse_version(our_min_version): min_version = our_min_version # Do the whole build-and-register-feed c = Command() c.run( ( "0launch", "--message", _("Download the 0compile tool, to compile the source code"), "--not-before=" + min_version, "http://0install.net/2006/interfaces/0compile.xml", "gui", interface_uri, ), lambda unused: on_success(), )
def testDownload(self): out, err = self.run_0install(["download"]) assert out.lower().startswith("usage:") assert "--show" in out out, err = self.run_0install(["download", "Local.xml", "--show"]) assert not err, err assert "Version: 0.1" in out local_uri = model.canonical_iface_uri("Local.xml") out, err = self.run_0install(["download", "Local.xml", "--xml"]) assert not err, err sels = selections.Selections(qdom.parse(StringIO(str(out)))) assert sels.selections[local_uri].version == "0.1" out, err = self.run_0install(["download", "Local.xml", "--show", "--with-store=/foo"]) assert not err, err assert self.config.stores.stores[-1].dir == "/foo" out, err = self.run_0install(["download", "--offline", "selections.xml"]) assert "Would download" in err self.config.network_use = model.network_full self.config.stores = TestStores() digest = "sha1=3ce644dc725f1d21cfcf02562c76f375944b266a" self.config.fetcher.allow_download(digest) out, err = self.run_0install(["download", "Hello.xml", "--show"]) assert not err, err assert self.config.stores.lookup_any([digest]).startswith("/fake") assert "Version: 1\n" in out out, err = self.run_0install(["download", "--offline", "selections.xml", "--show"]) assert "/fake_store" in out self.config.network_use = model.network_full
def action_help(self, uri): from zeroinstall.injector.config import load_config c = load_config() xml = subprocess.check_output(['0install', 'download', '--xml', '--', uri], universal_newlines = False) sels = selections.Selections(qdom.parse(BytesIO(xml))) impl = sels.selections[uri] assert impl, "Failed to choose an implementation of %s" % uri help_dir = impl.attrs.get('doc-dir') if impl.id.startswith('package:'): assert os.path.isabs(help_dir), "Package doc-dir must be absolute!" path = help_dir else: path = impl.local_path or c.stores.lookup_any(impl.digests) assert path, "Chosen implementation is not cached!" if help_dir: path = os.path.join(path, help_dir) else: main = impl.main if main: # Hack for ROX applications. They should be updated to # set doc-dir. help_dir = os.path.join(path, os.path.dirname(main), 'Help') if os.path.isdir(help_dir): path = help_dir # xdg-open has no "safe" mode, so check we're not "opening" an application. if os.path.exists(os.path.join(path, 'AppRun')): raise Exception(_("Documentation directory '%s' is an AppDir; refusing to open") % path) subprocess.Popen(['xdg-open', path])
def ask_if_previous_still_testing(master_doc, new_version): new_version_parsed = model.parse_version(new_version) xml = master_doc.toxml(encoding = 'utf-8') master = model.ZeroInstallFeed(qdom.parse(BytesIO(xml))) previous_versions = [impl.version for impl in master.implementations.values() if impl.version < new_version_parsed] if not previous_versions: return previous_version = max(previous_versions) # (all the <implementations> with this version number) previous_testing_impls = [impl for impl in master.implementations.values() if impl.version == previous_version and impl.upstream_stability == model.testing] if not previous_testing_impls: return print("The previous release, version {version}, is still marked as 'testing'. Set to stable?".format( version = model.format_version(previous_version))) if get_choice(['Yes', 'No']) != 'Yes': return ids_to_change = frozenset(impl.id for impl in previous_testing_impls) for impl in master_doc.getElementsByTagNameNS(XMLNS_IFACE, 'implementation'): if impl.getAttribute('id') in ids_to_change: impl.setAttribute('stability', 'stable')
def handle(config, options, args): if len(args) == 0: raise UsageError() url = config.mirror + '/search/?q=' + quote(' '.join(args)) logger.info("Fetching %s...", url) root = qdom.parse(urllib2.urlopen(url)) assert root.name == 'results' first = True for child in root.childNodes: if child.name != 'result': continue if first: first = False else: print() print(child.attrs['uri']) score = child.attrs['score'] details = {} for detail in child.childNodes: details[detail.name] = detail.content print(" {name} - {summary} [{score}%]".format( name = child.attrs['name'], summary = details.get('summary', ''), score = score))
def load_feed(source, local = False, selections_ok = False): """Load a feed from a local file. @param source: the name of the file to read @type source: str @param local: this is a local feed @type local: bool @param selections_ok: if it turns out to be a local selections document, return that instead @type selections_ok: bool @raise InvalidInterface: if the source's syntax is incorrect @return: the new feed @since: 0.48 @see: L{iface_cache.iface_cache}, which uses this to load the feeds""" try: with open(source, 'rb') as stream: root = qdom.parse(stream) except IOError as ex: if ex.errno == errno.ENOENT and local: raise MissingLocalFeed(_("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case.")) raise InvalidInterface(_("Can't read file"), ex) except Exception as ex: raise InvalidInterface(_("Invalid XML"), ex) if local: if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections': from zeroinstall.injector import selections return selections.Selections(root) local_path = source else: local_path = None feed = ZeroInstallFeed(root, local_path) feed.last_modified = int(os.stat(source).st_mtime) return feed
def testLocale(self): local_path = os.path.join(mydir, 'Local.xml') with open(local_path, 'rb') as stream: dom = qdom.parse(stream) feed = model.ZeroInstallFeed(dom, local_path = local_path) # (defaults to en-US if no language is set in the locale) self.assertEqual("Local feed (English)", feed.summary) self.assertEqual("English", feed.description) self.assertEqual(4, len(feed.summaries)) self.assertEqual(2, len(feed.descriptions)) try: basetest.test_locale = ('es_ES', 'UTF8') self.assertEqual("Fuente local", feed.summary) self.assertEqual("Español", feed.description) basetest.test_locale = ('en_GB', 'UTF8') self.assertEqual("Local feed (English GB)", feed.summary) basetest.test_locale = ('fr_FR', 'UTF8') self.assertEqual("Local feed (English)", feed.summary) self.assertEqual("English", feed.description) finally: basetest.test_locale = (None, None)
def handle(args): files = [abspath(f) for f in args.path] if not cmd.find_config(missing_ok=True): # Import into appropriate registry for this feed with open(files[0], 'rb') as stream: doc = qdom.parse(stream) master = incoming.get_feed_url(doc, files[0]) from_registry = registry.lookup(master) assert from_registry[ 'type'] == 'local', 'Unsupported registry type in %s' % from_registry os.chdir(from_registry['path']) print("Adding to registry '{path}'".format(path=from_registry['path'])) config = cmd.load_config() messages = [] for feed in files: print("Adding", feed) msg = incoming.process(config, feed, delete_on_success=False) if msg: messages.append(msg) update.do_update(config, messages=messages)
def testSelect(self): out, err = self.run_ocaml(['select']) assert out.lower().startswith("usage:") assert '--xml' in out out, err = self.run_ocaml(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_ocaml(['select', 'Local.xml', '--command=']) assert not err, err assert 'Version: 0.1' in out local_uri = os.path.realpath('Local.xml') out, err = self.run_ocaml(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_ocaml(['select', 'Local.xml', '--xml']) sels = selections.Selections(qdom.parse(BytesIO(str(out).encode('utf-8')))) assert sels.selections[local_uri].version == '0.1' # This now triggers a download to fetch the feed. #out, err = self.run_ocaml(['select', 'selections.xml']) #assert not err, err #assert 'Version: 1\n' in out #assert '(not cached)' in out out, err = self.run_ocaml(['select', 'runnable/RunExec.xml']) assert not err, err assert 'Runner' in out, out
def testSelect(self): out, err = self.run_0install(["select"]) assert out.lower().startswith("usage:") assert "--xml" in out out, err = self.run_0install(["select", "Local.xml"]) assert not err, err assert "Version: 0.1" in out out, err = self.run_0install(["select", "Local.xml", "--command="]) assert not err, err assert "Version: 0.1" in out local_uri = model.canonical_iface_uri("Local.xml") out, err = self.run_0install(["select", "Local.xml"]) assert not err, err assert "Version: 0.1" in out out, err = self.run_0install(["select", "Local.xml", "--xml"]) sels = selections.Selections(qdom.parse(StringIO(str(out)))) assert sels.selections[local_uri].version == "0.1" out, err = self.run_0install(["select", "selections.xml"]) assert not err, err assert "Version: 1\n" in out assert "(not cached)" in out out, err = self.run_0install(["select", "runnable/RunExec.xml"]) assert not err, err assert "Runner" in out, out
def handle(args): files = [abspath(f) for f in args.path] if not cmd.find_config(missing_ok = True): # Import into appropriate registry for this feed with open(files[0], 'rb') as stream: doc = qdom.parse(stream) master = incoming.get_feed_url(doc, files[0]) from_registry = registry.lookup(master) assert from_registry['type'] == 'local', 'Unsupported registry type in %s' % from_registry os.chdir(from_registry['path']) print("Adding to registry '{path}'".format(path = from_registry['path'])) config = cmd.load_config() messages = [] for feed in files: print("Adding", feed) msg = incoming.process(config, feed, delete_on_success = False) if msg: messages.append(msg) update.do_update(config, messages = messages)
def testDownload(self): out, err = self.run_0install(['download']) assert out.lower().startswith("usage:") assert '--show' in out out, err = self.run_0install(['download', 'Local.xml', '--show']) assert not err, err assert 'Version: 0.1' in out local_uri = model.canonical_iface_uri('Local.xml') out, err = self.run_0install(['download', 'Local.xml', '--xml']) assert not err, err sels = selections.Selections(qdom.parse(BytesIO(str(out).encode('utf-8')))) assert sels.selections[local_uri].version == '0.1' out, err = self.run_0install(['download', 'Local.xml', '--show', '--with-store=/foo']) assert not err, err assert self.config.stores.stores[-1].dir == '/foo' out, err = self.run_0install(['download', '--offline', 'selections.xml']) assert 'Would download' in err self.config.network_use = model.network_full self.config.stores = TestStores() digest = 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266a' self.config.fetcher.allow_download(digest) out, err = self.run_0install(['download', 'Hello.xml', '--show']) assert not err, err assert self.config.stores.lookup_any([digest]).startswith('/fake') assert 'Version: 1\n' in out out, err = self.run_0install(['download', '--offline', 'selections.xml', '--show']) assert '/fake_store' in out self.config.network_use = model.network_full
def load_feed(source, local=False): """Load a feed from a local file. @param source: the name of the file to read @type source: str @param local: this is a local feed @type local: bool @return: the new feed @rtype: L{ZeroInstallFeed} @raise InvalidInterface: if the source's syntax is incorrect @since: 0.48 @see: L{iface_cache.iface_cache}, which uses this to load the feeds""" try: with open(source, "rb") as stream: root = qdom.parse(stream, filter_for_version=True) except IOError as ex: if ex.errno == errno.ENOENT and local: raise MissingLocalFeed( _( "Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case." ) ) raise InvalidInterface(_("Can't read file"), ex) except Exception as ex: raise InvalidInterface(_("Invalid XML"), ex) if local: assert os.path.isabs(source), source local_path = source else: local_path = None feed = ZeroInstallFeed(root, local_path) feed.last_modified = int(os.stat(source).st_mtime) return feed
def get_selections(self, snapshot_date = None, may_update = False, use_gui = None): """Load the selections. If may_update is True then the returned selections will be cached and available. @param snapshot_date: get a historical snapshot @type snapshot_date: (as returned by L{get_history}) | None @param may_update: whether to check for updates @type may_update: bool @param use_gui: whether to use the GUI for foreground updates @type use_gui: bool | None (never/always/if possible) @return: the selections @rtype: L{selections.Selections}""" if snapshot_date: assert may_update is False, "Can't update a snapshot!" sels_file = os.path.join(self.path, 'selections-' + snapshot_date + '.xml') else: sels_file = os.path.join(self.path, 'selections.xml') try: with open(sels_file, 'rb') as stream: sels = selections.Selections(qdom.parse(stream)) except IOError as ex: if may_update and ex.errno == errno.ENOENT: logger.info("App selections missing: %s", ex) sels = None else: raise if may_update: sels = self._check_for_updates(sels, use_gui) return sels
def graduation_check(feeds, feeds_dir): # Warn about releases that are still 'testing' a while after release now = time.time() def age(impl): released = impl.metadata.get('released', None) if not released: return 0 released_time = time.mktime(time.strptime(released, '%Y-%m-%d')) return now - released_time shown_header = False for feed in feeds: with open(feed.source_path, 'rb') as stream: zfeed = model.ZeroInstallFeed(qdom.parse(stream)) if zfeed.implementations: # Find the latest version number (note that there may be several implementations with this version number) latest_version = max(impl.version for impl in zfeed.implementations.values()) testing_impls = [impl for impl in zfeed.implementations.values() if impl.version == latest_version and impl.upstream_stability == model.stability_levels['testing'] and age(impl) > TIME_TO_GRADUATE] if testing_impls: if not shown_header: print("Releases which are still marked as 'testing' after {days} days:".format( days = TIME_TO_GRADUATE / DAY)) shown_header = True print("- {name} v{version}, {age} days ({path})".format( age = int(age(testing_impls[0]) / DAY), name = zfeed.get_name(), path = os.path.relpath(feed.source_path, feeds_dir), version = model.format_version(latest_version)))
def testSelect(self): out, err = self.run_0install(['select']) assert out.lower().startswith("usage:") assert '--xml' in out out, err = self.run_0install(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_0install(['select', 'Local.xml', '--command=']) assert not err, err assert 'Version: 0.1' in out local_uri = model.canonical_iface_uri('Local.xml') out, err = self.run_0install(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_0install(['select', 'Local.xml', '--xml']) sels = selections.Selections(qdom.parse(BytesIO(str(out).encode('utf-8')))) assert sels.selections[local_uri].version == '0.1' out, err = self.run_0install(['select', 'selections.xml']) assert not err, err assert 'Version: 1\n' in out assert '(not cached)' in out out, err = self.run_0install(['select', 'runnable/RunExec.xml']) assert not err, err assert 'Runner' in out, out
def handle(config, options, args): if len(args) != 1: raise UsageError() app = config.app_mgr.lookup_app(args[0], missing_ok = True) if app is not None: sels = app.get_selections() r = app.get_requirements() if r.extra_restrictions and not options.xml: print("User-provided restrictions in force:") for uri, expr in r.extra_restrictions.items(): print(" {uri}: {expr}".format(uri = uri, expr = expr)) print() elif os.path.exists(args[0]): with open(args[0], 'rb') as stream: sels = selections.Selections(qdom.parse(stream)) else: raise SafeException(_("Neither an app nor a file: '%s'") % args[0]) if options.root_uri: print(sels.interface) elif options.xml: select.show_xml(sels) else: select.show_human(sels, config.stores)
def testSelect(self): out, err = self.run_0install(['select']) assert out.lower().startswith("usage:") assert '--xml' in out out, err = self.run_0install(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_0install(['select', 'Local.xml', '--command=']) assert not err, err assert 'Version: 0.1' in out local_uri = model.canonical_iface_uri('Local.xml') out, err = self.run_0install(['select', 'Local.xml']) assert not err, err assert 'Version: 0.1' in out out, err = self.run_0install(['select', 'Local.xml', '--xml']) sels = selections.Selections( qdom.parse(BytesIO(str(out).encode('utf-8')))) assert sels.selections[local_uri].version == '0.1' out, err = self.run_0install(['select', 'selections.xml']) assert not err, err assert 'Version: 1\n' in out assert '(not cached)' in out out, err = self.run_0install(['select', 'runnable/RunExec.xml']) assert not err, err assert 'Runner' in out, out
def testLocale(self): local_path = os.path.join(mydir, 'Local.xml') with open(local_path, 'rb') as stream: dom = qdom.parse(stream) feed = model.ZeroInstallFeed(dom, local_path=local_path) # (defaults to en-US if no language is set in the locale) self.assertEqual("Local feed (English)", feed.summary) self.assertEqual("English", feed.description) self.assertEqual(4, len(feed.summaries)) self.assertEqual(2, len(feed.descriptions)) try: basetest.test_locale = ('es_ES', 'UTF8') self.assertEqual("Fuente local", feed.summary) self.assertEqual("Español", feed.description) basetest.test_locale = ('en_GB', 'UTF8') self.assertEqual("Local feed (English GB)", feed.summary) basetest.test_locale = ('fr_FR', 'UTF8') self.assertEqual("Local feed (English)", feed.summary) self.assertEqual("English", feed.description) finally: basetest.test_locale = (None, None)
def handle(config, options, args): if len(args) == 0: raise UsageError() url = config.mirror + '/search/?q=' + quote(' '.join(args)) logger.info("Fetching %s...", url) root = qdom.parse(urllib2.urlopen(url)) assert root.name == 'results' first = True for child in root.childNodes: if child.name != 'result': continue if first: first = False else: print() print(child.attrs['uri']) score = child.attrs['score'] details = {} for detail in child.childNodes: details[detail.name] = detail.content print(" {name} - {summary} [{score}%]".format( name=child.attrs['name'], summary=details.get('summary', ''), score=score))
def testCommands(self): iface = os.path.join(mydir, "Command.xml") p = policy.Policy(iface, config = self.config) p.need_download() assert p.ready impl = p.solver.selections[self.config.iface_cache.get_interface(iface)] assert impl.id == 'c' assert impl.main == 'runnable/missing' dep_impl_uri = impl.commands['run'].requires[0].interface dep_impl = p.solver.selections[self.config.iface_cache.get_interface(dep_impl_uri)] assert dep_impl.id == 'sha1=256' s1 = selections.Selections(p) assert s1.commands[0].path == 'runnable/missing' xml = s1.toDOM().toxml("utf-8") root = qdom.parse(StringIO(xml)) s2 = selections.Selections(root) assert s2.commands[0].path == 'runnable/missing' impl = s2.selections[iface] assert impl.id == 'c' assert s2.commands[0].qdom.attrs['http://custom attr'] == 'namespaced' custom_element = s2.commands[0].qdom.childNodes[0] assert custom_element.name == 'child' dep_impl = s2.selections[dep_impl_uri] assert dep_impl.id == 'sha1=256'
def testCommands(self): iface = os.path.join(mydir, "Command.xml") p = policy.Policy(iface, config=self.config) p.need_download() assert p.ready impl = p.solver.selections[self.config.iface_cache.get_interface( iface)] assert impl.id == 'c' assert impl.main == 'runnable/missing' dep_impl_uri = impl.commands['run'].requires[0].interface dep_impl = p.solver.selections[self.config.iface_cache.get_interface( dep_impl_uri)] assert dep_impl.id == 'sha1=256' s1 = selections.Selections(p) assert s1.commands[0].path == 'runnable/missing' xml = s1.toDOM().toxml("utf-8") root = qdom.parse(StringIO(xml)) s2 = selections.Selections(root) assert s2.commands[0].path == 'runnable/missing' impl = s2.selections[iface] assert impl.id == 'c' assert s2.commands[0].qdom.attrs['http://custom attr'] == 'namespaced' custom_element = s2.commands[0].qdom.childNodes[0] assert custom_element.name == 'child' dep_impl = s2.selections[dep_impl_uri] assert dep_impl.id == 'sha1=256'
def handle_invoke(config, options, ticket, request): try: command = request[0] logger.debug("Got request '%s'", command) if command == 'get-selections-gui': response = do_get_selections_gui(config, request[1:]) elif command == 'wait-for-network': response = do_wait_for_network(config) elif command == 'download-selections': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) blocker = do_download_selections(config, options, request[1:], xml) reply_when_done(ticket, blocker) return #async elif command == 'get-package-impls': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) response = do_get_package_impls(config, options, request[1:], xml) elif command == 'is-distro-package-installed': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) response = do_is_distro_package_installed(config, options, xml) elif command == 'get-distro-candidates': l = stdin.readline().strip() xml = qdom.parse(BytesIO(stdin.read(int(l)))) blocker = do_get_distro_candidates(config, request[1:], xml) reply_when_done(ticket, blocker) return # async elif command == 'download-and-import-feed': blocker = do_download_and_import_feed(config, request[1:]) reply_when_done(ticket, blocker) return # async elif command == 'notify-user': response = do_notify_user(config, request[1]) else: raise SafeException("Internal error: unknown command '%s'" % command) response = ['ok', response] except SafeException as ex: logger.info("Replying with error: %s", ex) response = ['error', str(ex)] except Exception as ex: import traceback logger.info("Replying with error: %s", ex) response = ['error', traceback.format_exc().strip()] send_json(["return", ticket, response])
def testSitePackages(self): # The old system (0install < 1.9): # - 0compile stores implementations to ~/.cache, and # - adds to extra_feeds # # The middle system (0install 1.9..1.12) # - 0compile stores implementations to ~/.local/0install.net/site-packages # but using an obsolete escaping scheme, and # - modern 0install finds them via extra_feeds # # The new system (0install >= 1.13): # - 0compile stores implementations to ~/.local/0install.net/site-packages, and # - 0install finds them automatically # For backwards compatibility, 0install >= 1.9: # - writes discovered feeds to extra_feeds # - skips such entries in extra_feeds when loading expected_escape = 'section__prog_5f_1.xml' meta_dir = basedir.save_data_path('0install.net', 'site-packages', 'http', 'example.com', expected_escape, '1.0', '0install') feed = os.path.join(meta_dir, 'feed.xml') shutil.copyfile(os.path.join(mydir, 'Local.xml'), feed) # Check that we find the feed without us having to register it iface = self.config.iface_cache.get_interface('http://example.com/section/prog_1.xml') self.assertEqual(1, len(iface.extra_feeds)) site_feed, = iface.extra_feeds self.assertEqual(True, site_feed.site_package) # Check that we write it out, so that older 0installs can find it writer.save_interface(iface) config_file = basedir.load_first_config('0install.net', 'injector', 'interfaces', 'http:##example.com#section#prog_1.xml') with open(config_file, 'rb') as s: doc = qdom.parse(s) feed_node = None for item in doc.childNodes: if item.name == 'feed': feed_node = item self.assertEqual('True', feed_node.getAttribute('is-site-package')) # Check we ignore this element iface.reset() self.assertEqual([], iface.extra_feeds) reader.update_user_overrides(iface) self.assertEqual([], iface.extra_feeds) # Check feeds are automatically removed again reader.update_from_cache(iface, iface_cache = self.config.iface_cache) self.assertEqual(1, len(iface.extra_feeds)) shutil.rmtree(basedir.load_first_data('0install.net', 'site-packages', 'http', 'example.com', expected_escape)) reader.update_from_cache(iface, iface_cache = self.config.iface_cache) self.assertEqual(0, len(iface.extra_feeds))
def load_built_feed(self): path = self.local_iface_file stream = file(path) try: feed = model.ZeroInstallFeed(qdom.parse(stream), local_path = path) finally: stream.close() return feed
def testRecipeRemoveDir(self): run_server(('HelloWorld.tar.bz2',)) uri = os.path.abspath('RecipeRemoveDir.xml') out, err = self.run_ocaml(['download', uri, '--command=', '--xml'], binary = True) sels = selections.Selections(qdom.parse(BytesIO(out))) digests = sels.selections[uri].digests path = self.config.stores.lookup_any(digests) assert not os.path.exists(os.path.join(path, 'HelloWorld'))
def testMetadata(self): main_feed = model.ZeroInstallFeed(empty_feed, local_path = '/foo') e = qdom.parse(StringIO('<ns:b xmlns:ns="a" foo="bar"/>')) main_feed.metadata = [e] assert main_feed.get_metadata('a', 'b') == [e] assert main_feed.get_metadata('b', 'b') == [] assert main_feed.get_metadata('a', 'a') == [] assert e.getAttribute('foo') == 'bar'
def testExtractToNewSubdirectory(self): run_server(('HelloWorld.tar.bz2',)) uri = os.path.abspath('HelloExtractToNewDest.xml') out, err = self.run_ocaml(['download', uri, '--command=', '--xml'], binary = True) sels = selections.Selections(qdom.parse(BytesIO(out))) digests = sels.selections[uri].digests path = self.config.stores.lookup_any(digests) assert os.path.exists(os.path.join(path, 'src', 'HelloWorld', 'main'))
def test(top_xml, diag_xml, expected_error): root = qdom.parse( BytesIO("""<?xml version="1.0" ?> <interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface" uri="{top}"> <name>Top-level</name> <summary>Top-level</summary> <group> {top_xml} </group> </interface>""".format(top=top_uri, top_xml=top_xml).encode("utf-8"))) self.import_feed(top_uri, root) root = qdom.parse( BytesIO("""<?xml version="1.0" ?> <interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface" uri="{diag}"> <name>Diagnostics</name> <summary>Diagnostics</summary> <group> {impls} </group> </interface>""".format(diag=diag_uri, impls=diag_xml).encode("utf-8"))) self.import_feed(diag_uri, root) root = qdom.parse( BytesIO("""<?xml version="1.0" ?> <interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface" uri="{old}"> <name>Old</name> <summary>Old</summary> <feed src='{diag}'/> <replaced-by interface='{diag}'/> </interface>""".format(diag=diag_uri, old=old_uri).encode("utf-8"))) self.import_feed(old_uri, root) r = Requirements(top_uri) r.os = "Windows" r.cpu = "x86_64" s = solver.DefaultSolver(self.config) s.solve_for(r) assert not s.ready, s.selections.selections if expected_error != str(s.get_failure_reason()): print(s.get_failure_reason()) self.assertEqual(expected_error, str(s.get_failure_reason())) return s
def testLocalArchive(self): local_iface = os.path.join(mydir, 'LocalArchive.xml') with open(local_iface, 'rb') as stream: root = qdom.parse(stream) # Not local => error feed = model.ZeroInstallFeed(root) impl = feed.implementations['impl1'] blocker = self.config.fetcher.download_impls([impl], self.config.stores) try: tasks.wait_for_blocker(blocker) assert 0 except model.SafeException as ex: assert "Relative URL 'HelloWorld.tgz' in non-local feed" in str( ex), ex feed = model.ZeroInstallFeed(root, local_path=local_iface) # Missing file impl2 = feed.implementations['impl2'] blocker = self.config.fetcher.download_impls([impl2], self.config.stores) try: tasks.wait_for_blocker(blocker) assert 0 except model.SafeException as ex: assert 'tests/IDONTEXIST.tgz' in str(ex), ex # Wrong size impl3 = feed.implementations['impl3'] blocker = self.config.fetcher.download_impls([impl3], self.config.stores) try: tasks.wait_for_blocker(blocker) assert 0 except model.SafeException as ex: assert 'feed says 177, but actually 176 bytes' in str(ex), ex self.config.network_use = model.network_offline r = Requirements(local_iface) r.command = None driver = Driver(requirements=r, config=self.config) driver.need_download() assert driver.solver.ready, driver.solver.get_failure_reason() # Local => OK impl = feed.implementations['impl1'] path = self.config.stores.lookup_maybe(impl.digests) assert not path blocker = self.config.fetcher.download_impls([impl], self.config.stores) tasks.wait_for_blocker(blocker) path = self.config.stores.lookup_any(impl.digests) assert os.path.exists(os.path.join(path, 'HelloWorld'))
def load_built_selections(self): path = join(self.metadir, 'build-environment.xml') if os.path.exists(path): stream = file(path) try: return selections.Selections(qdom.parse(stream)) finally: stream.close() return None
def parse_impls(impls): xml = """<?xml version="1.0" ?> <interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface"> <name>Foo</name> <summary>Foo</summary> <description>Foo</description> {impls} </interface>""".format(impls=impls) element = qdom.parse(BytesIO(xml.encode('utf-8'))) return model.ZeroInstallFeed(element, "myfeed.xml")
def parse_impls(impls): xml = """<?xml version="1.0" ?> <interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface"> <name>Foo</name> <summary>Foo</summary> <description>Foo</description> %s </interface>""" % impls element = qdom.parse(StringIO(xml)) return model.ZeroInstallFeed(element, "myfeed.xml")
def testCommands(self): iface = os.path.join(mydir, "Command.xml") driver = Driver(requirements=Requirements(iface), config=self.config) driver.need_download() assert driver.solver.ready impl = driver.solver.selections[self.config.iface_cache.get_interface( iface)] assert impl.id == 'c' assert impl.main == 'test-gui' dep_impl_uri = impl.commands['run'].requires[0].interface dep_impl = driver.solver.selections[ self.config.iface_cache.get_interface(dep_impl_uri)] assert dep_impl.id == 'sha1=256' s1 = driver.solver.selections assert s1.commands[0].path == 'test-gui' xml = s1.toDOM().toxml("utf-8") root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) assert s2.commands[0].path == 'test-gui' impl = s2.selections[iface] assert impl.id == 'c' assert s2.commands[0].qdom.attrs['http://custom attr'] == 'namespaced' custom_element = s2.commands[0].qdom.childNodes[0] assert custom_element.name == 'child' dep_impl = s2.selections[dep_impl_uri] assert dep_impl.id == 'sha1=256' d = Driver(self.config, requirements.Requirements(runexec)) need_download = d.need_download() assert need_download == False xml = d.solver.selections.toDOM().toxml("utf-8") root = qdom.parse(BytesIO(xml)) s3 = selections.Selections(root) runnable_impl = s3.selections[runnable] assert 'foo' in runnable_impl.commands assert 'run' in runnable_impl.commands
def testMetadata(self): main_feed = model.ZeroInstallFeed(empty_feed, local_path='/foo') assert main_feed.local_path == "/foo" e = qdom.parse(BytesIO(b'<ns:b xmlns:ns="a" foo="bar"/>')) main_feed.metadata = [e] assert main_feed.get_metadata('a', 'b') == [e] assert main_feed.get_metadata('b', 'b') == [] assert main_feed.get_metadata('a', 'a') == [] assert e.getAttribute('foo') == 'bar' self.assertEqual(None, main_feed.get_replaced_by())
def testLocalPath(self): # 0launch --get-selections Local.xml iface = os.path.join(mydir, "Local.xml") driver = Driver(requirements=Requirements(iface), config=self.config) driver.need_download() assert driver.solver.ready s1 = driver.solver.selections xml = s1.toDOM().toxml("utf-8") # Reload selections and check they're the same root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) local_path = s2.selections[iface].local_path assert os.path.isdir(local_path), local_path assert not s2.selections[iface].digests, s2.selections[iface].digests # Add a newer implementation and try again feed = self.config.iface_cache.get_feed(iface) impl = model.ZeroInstallImplementation(feed, "foo bar=123", local_path=None) impl.version = model.parse_version('1.0') impl.commands["run"] = model.Command( qdom.Element(namespaces.XMLNS_IFACE, 'command', { 'path': 'dummy', 'name': 'run' }), None) impl.add_download_source('http://localhost/bar.tgz', 1000, None) feed.implementations = {impl.id: impl} assert driver.need_download() assert driver.solver.ready, driver.solver.get_failure_reason() s1 = driver.solver.selections xml = s1.toDOM().toxml("utf-8") root = qdom.parse(BytesIO(xml)) s2 = selections.Selections(root) xml = s2.toDOM().toxml("utf-8") qdom.parse(BytesIO(xml)) assert s2.selections[iface].local_path is None assert not s2.selections[iface].digests, s2.selections[iface].digests assert s2.selections[iface].id == 'foo bar=123'
def get_selections(self, snapshot_date=None): """Load the selections. Does not check whether they are cached, nor trigger updates. @param snapshot_date: get a historical snapshot @type snapshot_date: (as returned by L{get_history}) | None @return: the selections @rtype: L{selections.Selections}""" if snapshot_date: sels_file = os.path.join(self.path, 'selections-' + snapshot_date + '.xml') else: sels_file = os.path.join(self.path, 'selections.xml') with open(sels_file, 'rb') as stream: return selections.Selections(qdom.parse(stream))
def testSitePackages(self): # The old system (0install < 1.9): # - 0compile stores implementations to ~/.cache, and # - adds to extra_feeds # The new system (0install >= 1.9): # - 0compile stores implementations to ~/.local/0install.net/site-packages, and # - 0install finds them automatically # For backwards compatibility, 0install >= 1.9: # - writes discovered feeds to extra_feeds # - skips such entries in extra_feeds when loading meta_dir = basedir.save_data_path('0install.net', 'site-packages', 'http:##example.com#prog.xml', '1.0', '0install') feed = os.path.join(meta_dir, 'feed.xml') shutil.copyfile(os.path.join(mydir, 'Local.xml'), feed) # Check that we find the feed without us having to register it iface = self.config.iface_cache.get_interface('http://example.com/prog.xml') self.assertEqual(1, len(iface.extra_feeds)) site_feed, = iface.extra_feeds self.assertEqual(True, site_feed.site_package) # Check that we write it out, so that older 0installs can find it writer.save_interface(iface) config_file = basedir.load_first_config('0install.net', 'injector', 'interfaces', 'http:##example.com#prog.xml') with open(config_file, 'rb') as s: doc = qdom.parse(s) feed_node = None for item in doc.childNodes: if item.name == 'feed': feed_node = item self.assertEqual('True', feed_node.getAttribute('site-package')) # Check we ignore this element iface.reset() self.assertEqual([], iface.extra_feeds) reader.update_user_overrides(iface) self.assertEqual([], iface.extra_feeds) # Check feeds are automatically removed again reader.update_from_cache(iface, iface_cache = self.config.iface_cache) self.assertEqual(1, len(iface.extra_feeds)) shutil.rmtree(basedir.load_first_data('0install.net', 'site-packages', 'http:##example.com#prog.xml')) reader.update_from_cache(iface, iface_cache = self.config.iface_cache) self.assertEqual(0, len(iface.extra_feeds))
def update_user_overrides(interface, known_site_feeds=frozenset()): """Update an interface with user-supplied information. Sets preferred stability and updates extra_feeds. @param interface: the interface object to update @type interface: L{model.Interface} @param known_site_feeds: feeds to ignore (for backwards compatibility) """ user = basedir.load_first_config(config_site, config_prog, 'interfaces', model._pretty_escape(interface.uri)) if user is None: # For files saved by 0launch < 0.49 user = basedir.load_first_config(config_site, config_prog, 'user_overrides', escape(interface.uri)) if not user: return try: with open(user, 'rb') as stream: root = qdom.parse(stream) except Exception as ex: logger.warn(_("Error reading '%(user)s': %(exception)s"), { 'user': user, 'exception': ex }) raise stability_policy = root.getAttribute('stability-policy') if stability_policy: interface.set_stability_policy(stability_levels[str(stability_policy)]) for item in root.childNodes: if item.uri != XMLNS_IFACE: continue if item.name == 'feed': feed_src = item.getAttribute('src') if not feed_src: raise InvalidInterface(_('Missing "src" attribute in <feed>')) # (note: 0install 1.9..1.12 used a different scheme and the "site-package" attribute; # we deliberately use a different attribute name to avoid confusion) if item.getAttribute('is-site-package'): # Site packages are detected earlier. This test isn't completely reliable, # since older versions will remove the attribute when saving the config # (hence the next test). continue if feed_src in known_site_feeds: continue interface.extra_feeds.append( Feed(feed_src, item.getAttribute('arch'), True, langs=item.getAttribute('langs')))
def testSelectOnly(self): os.environ['DISPLAY'] = ':foo' out, err = self.run_0install(['select', '--xml', 'Hello.xml']) self.assertEqual("", err) assert out.endswith("Finished\n"), out out = out[:-len("Finished\n")] root = qdom.parse(BytesIO(str(out).encode('utf-8'))) self.assertEqual(namespaces.XMLNS_IFACE, root.uri) sels = selections.Selections(root) sel, = sels.selections.values() self.assertEqual("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a", sel.id)