In unit-tests, always use StringIO, not io
[zeroinstall.git] / tests / testmodel.py
bloba66fa000564691afbb72d245ebe668b0576a3d03
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 from __future__ import unicode_literals
4 import basetest
5 from basetest import BaseTest, empty_feed
6 import sys, os
7 from xml.dom import minidom
8 import unittest
9 from io import BytesIO
11 sys.path.insert(0, '..')
12 from zeroinstall.injector import model, qdom, namespaces
14 mydir = os.path.dirname(os.path.abspath(__file__))
16 class TestModel(BaseTest):
17 def testLevels(self):
18 assert model.network_offline in model.network_levels
19 assert model.network_minimal in model.network_levels
20 assert model.network_full in model.network_levels
22 def testStabilities(self):
23 assert 'insecure' in model.stability_levels
24 assert 'buggy' in model.stability_levels
25 assert 'developer' in model.stability_levels
26 assert 'testing' in model.stability_levels
27 assert 'stable' in model.stability_levels
28 assert 'preferred' in model.stability_levels
29 str(model.insecure)
31 def testEscape(self):
32 self.assertEqual("", model.escape(""))
33 self.assertEqual("hello", model.escape("hello"))
34 self.assertEqual("%20", model.escape(" "))
36 self.assertEqual("file%3a%2f%2ffoo%7ebar",
37 model.escape("file://foo~bar"))
38 self.assertEqual("file%3a%2f%2ffoo%25bar",
39 model.escape("file://foo%bar"))
41 self.assertEqual("file:##foo%7ebar",
42 model._pretty_escape("file://foo~bar"))
43 self.assertEqual("file:##foo%25bar",
44 model._pretty_escape("file://foo%bar"))
46 def testUnescape(self):
47 self.assertEqual("", model.unescape(""))
48 self.assertEqual("hello", model.unescape("hello"))
49 self.assertEqual(" ", model.unescape("%20"))
51 self.assertEqual("file://foo~bar",
52 model.unescape("file%3a%2f%2ffoo%7ebar"))
53 self.assertEqual("file://foo%bar",
54 model.unescape("file%3a%2f%2ffoo%25bar"))
56 self.assertEqual("file://foo",
57 model.unescape("file:##foo"))
58 self.assertEqual("file://foo~bar",
59 model.unescape("file:##foo%7ebar"))
60 self.assertEqual("file://foo%bar",
61 model.unescape("file:##foo%25bar"))
63 def testEscaping(self):
64 def check(str):
65 self.assertEqual(str, model.unescape(model.escape(str)))
66 self.assertEqual(str, model.unescape(model._pretty_escape(str)))
68 check('http://example.com')
69 check('http://example%46com')
70 check('http:##example#com')
71 check('http://example.com/foo/bar.xml')
72 check('%20%21~&!"£ :@;,./{}$%^&()')
74 def testBadInterface(self):
75 try:
76 model.Interface('foo')
77 assert 0
78 except model.SafeException:
79 pass
81 def testInterface(self):
82 i = model.Interface('http://foo')
83 self.assertEqual('(foo)', i.get_name())
84 feed = model.ZeroInstallFeed(empty_feed, local_path = '/foo')
85 self.assertEqual('Empty', feed.get_name())
86 repr(i)
88 def testMetadata(self):
89 main_feed = model.ZeroInstallFeed(empty_feed, local_path = '/foo')
90 assert main_feed.local_path == "/foo"
91 e = qdom.parse(BytesIO(b'<ns:b xmlns:ns="a" foo="bar"/>'))
92 main_feed.metadata = [e]
93 assert main_feed.get_metadata('a', 'b') == [e]
94 assert main_feed.get_metadata('b', 'b') == []
95 assert main_feed.get_metadata('a', 'a') == []
96 assert e.getAttribute('foo') == 'bar'
97 self.assertEqual(None, main_feed.get_replaced_by())
99 def testLocale(self):
100 local_path = os.path.join(mydir, 'Local.xml')
101 with open(local_path, 'rb') as stream:
102 dom = qdom.parse(stream)
103 feed = model.ZeroInstallFeed(dom, local_path = local_path)
104 # (defaults to en-US if no language is set in the locale)
105 self.assertEqual("Local feed (English)", feed.summary)
106 self.assertEqual("English", feed.description)
108 self.assertEqual(4, len(feed.summaries))
109 self.assertEqual(2, len(feed.descriptions))
111 try:
112 basetest.test_locale = ('es_ES', 'UTF8')
114 self.assertEqual("Fuente local", feed.summary)
115 self.assertEqual("Español", feed.description)
117 basetest.test_locale = ('en_GB', 'UTF8')
119 self.assertEqual("Local feed (English GB)", feed.summary)
121 basetest.test_locale = ('fr_FR', 'UTF8')
123 self.assertEqual("Local feed (English)", feed.summary)
124 self.assertEqual("English", feed.description)
125 finally:
126 basetest.test_locale = (None, None)
128 def testCommand(self):
129 local_path = os.path.join(mydir, 'Command.xml')
130 with open(local_path, 'rb') as stream:
131 dom = qdom.parse(stream)
132 feed = model.ZeroInstallFeed(dom, local_path = local_path)
134 assert feed.implementations['a'].main == 'foo'
135 assert feed.implementations['a'].commands['run'].path == 'foo'
136 assert feed.implementations['a'].commands['test'].path == 'test-foo'
138 assert feed.implementations['b'].main == 'bar'
139 assert feed.implementations['b'].commands['run'].path == 'bar'
140 assert feed.implementations['b'].commands['test'].path == 'test-foo'
142 assert feed.implementations['c'].main == 'test-gui'
143 assert feed.implementations['c'].commands['run'].path == 'test-gui'
144 assert feed.implementations['c'].commands['test'].path == 'test-baz'
146 def testStabPolicy(self):
147 i = model.Interface('http://foo')
148 self.assertEqual(None, i.stability_policy)
149 i.set_stability_policy(model.buggy)
150 self.assertEqual(model.buggy, i.stability_policy)
152 def testImpl(self):
153 f = model.ZeroInstallFeed(None)
154 f.url = 'http://foo'
155 a = model.ZeroInstallImplementation(f, 'foo', None)
156 assert a.id == 'foo'
157 assert a.size == a.version == a.user_stability == None
158 assert a.arch == a.upstream_stability == None
159 assert a.dependencies == {}
160 assert a.download_sources == []
161 assert a.get_stability() is model.testing
162 a.upstream_stability = model.stable
163 assert a.get_stability() is model.stable
164 a.user_stability = model.buggy
165 assert a.get_stability() is model.buggy
166 a.version = model.parse_version('1.2.3')
167 self.assertEqual('1.2.3', a.get_version())
168 a.version = model.parse_version('1.2.3-rc2-post')
169 self.assertEqual('1.2.3-rc2-post', a.get_version())
170 assert str(a) == 'foo'
172 b = model.ZeroInstallImplementation(f, 'foo', None)
173 b.version = model.parse_version("1.2.1")
174 assert b > a
176 def testDownloadSource(self):
177 f = model.ZeroInstallFeed(empty_feed, local_path = '/foo')
178 a = model.ZeroInstallImplementation(f, 'foo', None)
179 a.add_download_source('ftp://foo', 1024, None)
180 a.add_download_source('ftp://foo.tgz', 1025, 'foo')
181 assert a.download_sources[0].url == 'ftp://foo'
182 assert a.download_sources[0].size == 1024
183 assert a.download_sources[0].extract == None
184 assert a.feed is f
186 def testEnvBind(self):
187 a = model.EnvironmentBinding('PYTHONPATH', 'path')
188 assert a.name == 'PYTHONPATH'
189 assert a.insert == 'path'
190 str(a)
192 def testEnvModes(self):
193 prepend = model.EnvironmentBinding('PYTHONPATH', 'lib', None, model.EnvironmentBinding.PREPEND)
194 assert prepend.name == 'PYTHONPATH'
195 assert prepend.insert == 'lib'
196 assert prepend.mode is model.EnvironmentBinding.PREPEND
198 self.assertEqual('/impl/lib:/usr/lib', prepend.get_value('/impl', '/usr/lib'))
199 self.assertEqual('/impl/lib', prepend.get_value('/impl', None))
201 append = model.EnvironmentBinding('PYTHONPATH', 'lib', '/opt/lib', model.EnvironmentBinding.APPEND)
202 assert append.name == 'PYTHONPATH'
203 assert append.insert == 'lib'
204 assert append.mode is model.EnvironmentBinding.APPEND
206 self.assertEqual('/usr/lib:/impl/lib', append.get_value('/impl', '/usr/lib'))
207 self.assertEqual('/opt/lib:/impl/lib', append.get_value('/impl', None))
209 append = model.EnvironmentBinding('PYTHONPATH', 'lib', None, model.EnvironmentBinding.REPLACE)
210 assert append.name == 'PYTHONPATH'
211 assert append.insert == 'lib'
212 assert append.mode is model.EnvironmentBinding.REPLACE
214 self.assertEqual('/impl/lib', append.get_value('/impl', '/usr/lib'))
215 self.assertEqual('/impl/lib', append.get_value('/impl', None))
217 assert model.EnvironmentBinding('PYTHONPATH', 'lib').mode == model.EnvironmentBinding.PREPEND
219 def testOverlay(self):
220 for xml, expected in [(b'<overlay/>', '<overlay . on />'),
221 (b'<overlay src="usr"/>', '<overlay usr on />'),
222 (b'<overlay src="package" mount-point="/usr/games"/>', '<overlay package on /usr/games>')]:
223 e = qdom.parse(BytesIO(xml))
224 ol = model.process_binding(e)
225 self.assertEqual(expected, str(ol))
227 doc = minidom.parseString('<doc/>')
228 new_xml = ol._toxml(doc, None).toxml(encoding = 'utf-8')
229 new_e = qdom.parse(BytesIO(new_xml))
230 new_ol = model.process_binding(new_e)
231 self.assertEqual(expected, str(new_ol))
233 def testReplaced(self):
234 local_path = os.path.join(mydir, 'Replaced.xml')
235 with open(local_path, 'rb') as stream:
236 dom = qdom.parse(stream)
237 feed = model.ZeroInstallFeed(dom, local_path = local_path)
238 self.assertEqual("http://localhost:8000/Hello", feed.get_replaced_by())
240 def testDep(self):
241 b = model.InterfaceDependency('http://foo', element = qdom.Element(namespaces.XMLNS_IFACE, 'requires', {}))
242 assert not b.restrictions
243 assert not b.bindings
244 str(b)
246 def testFeed(self):
247 f = model.Feed('http://feed', arch = None, user_override = False)
248 assert f.uri == 'http://feed'
249 assert f.os == None
250 assert f.machine == None
251 assert f.arch == None
252 assert f.user_override == False
254 f = model.Feed('http://feed', arch = 'Linux-*', user_override = True)
255 assert f.uri == 'http://feed'
256 assert f.os == 'Linux'
257 assert f.machine == None
258 assert f.arch == 'Linux-*'
259 assert f.user_override == True
261 f = model.Feed('http://feed', arch = '*-i386', user_override = True)
262 assert f.uri == 'http://feed'
263 assert f.os == None
264 assert f.machine == 'i386'
265 assert f.arch == '*-i386'
266 assert f.user_override == True
267 assert str(f).startswith('<Feed from')
269 try:
270 f = model.Feed('http://feed', arch = 'i386', user_override = True)
271 assert False
272 except model.SafeException as ex:
273 assert 'Malformed arch' in str(ex)
275 def testCanonical(self):
276 # HTTP
277 try:
278 model.canonical_iface_uri('http://foo')
279 assert False
280 except model.SafeException as ex:
281 assert 'Missing /' in str(ex)
283 self.assertEqual('http://foo/',
284 model.canonical_iface_uri('http://foo/'))
285 try:
286 model.canonical_iface_uri('bad-name')
287 assert False
288 except model.SafeException as ex:
289 assert 'Bad interface name' in str(ex)
291 # Bare relative path
292 model.canonical_iface_uri('Command.xml')
293 try:
294 model.canonical_iface_uri('CommandMissing.xml')
295 assert False
296 except model.SafeException as ex:
297 assert "Bad interface name 'CommandMissing.xml'" in str(ex), ex
299 # file:absolute
300 model.canonical_iface_uri('file://{path}/Command.xml'.format(path = mydir))
301 try:
302 model.canonical_iface_uri('file://{path}/CommandMissing.xml'.format(path = mydir))
303 assert False
304 except model.SafeException as ex:
305 assert "Bad interface name 'file://" in str(ex), ex
307 # file:relative
308 model.canonical_iface_uri('file:Command.xml')
309 try:
310 model.canonical_iface_uri('file:CommandMissing.xml')
311 assert False
312 except model.SafeException as ex:
313 assert "Bad interface name 'file:CommandMissing.xml'" in str(ex), ex
316 def testVersions(self):
317 def pv(v):
318 parsed = model.parse_version(v)
319 assert model.format_version(parsed) == v
320 return parsed
322 assert pv('1.0') > pv('0.9')
323 assert pv('1.0') > pv('1')
324 assert pv('1.0') == pv('1.0')
325 assert pv('0.9.9') < pv('1.0')
326 assert pv('10') > pv('2')
328 def invalid(v):
329 try:
330 pv(v)
331 assert False
332 except model.SafeException:
333 pass
334 invalid('.')
335 invalid('hello')
336 invalid('2./1')
337 invalid('.1')
338 invalid('')
340 # Check parsing
341 assert pv('1') == [[1], 0]
342 assert pv('1.0') == [[1,0], 0]
343 assert pv('1.0-pre5') == [[1,0], -2, [5], 0]
344 assert pv('1.0-rc5') == [[1,0], -1, [5], 0]
345 assert pv('1.0-5') == [[1,0], 0, [5], 0]
346 assert pv('1.0-post5') == [[1,0], 1, [5], 0]
347 assert pv('1.0-post') == [[1,0], 1]
348 assert pv('1-rc2.0-pre2-post') == [[1], -1, [2,0], -2, [2], 1]
349 assert pv('1-rc2.0-pre-post') == [[1], -1, [2,0], -2, [], 1]
351 assert pv('1.0-0') > pv('1.0')
352 assert pv('1.0-1') > pv('1.0-0')
353 assert pv('1.0-0') < pv('1.0-1')
355 assert pv('1.0-pre99') > pv('1.0-pre1')
356 assert pv('1.0-pre99') < pv('1.0-rc1')
357 assert pv('1.0-rc1') < pv('1.0')
358 assert pv('1.0') < pv('1.0-0')
359 assert pv('1.0-0') < pv('1.0-post')
360 assert pv('2.1.9-pre-1') > pv('2.1.9-pre')
362 assert pv('2-post999') < pv('3-pre1')
364 if __name__ == '__main__':
365 unittest.main()