Fixed package-implementation scoring
[zeroinstall/solver.git] / tests / testsolver.py
blob475ac9bb7e8370b4e50cf6ee4b42a977497adaf0
1 #!/usr/bin/env python
2 from basetest import BaseTest
3 import sys, os, locale
4 import unittest
6 sys.path.insert(0, '..')
7 from zeroinstall.injector import solver, arch, model
8 from zeroinstall.injector.requirements import Requirements
10 import logging
11 logger = logging.getLogger()
12 #logger.setLevel(logging.DEBUG)
14 mydir = os.path.dirname(os.path.abspath(__file__))
15 command_dep = os.path.join(mydir, 'command-dep.xml')
17 class TestSolver(BaseTest):
18 def testSimple(self):
19 iface_cache = self.config.iface_cache
20 s = solver.DefaultSolver(self.config)
22 foo = iface_cache.get_interface('http://foo/Binary.xml')
23 self.import_feed(foo.uri, 'Binary.xml')
24 foo_src = iface_cache.get_interface('http://foo/Source.xml')
25 self.import_feed(foo_src.uri, 'Source.xml')
26 compiler = iface_cache.get_interface('http://foo/Compiler.xml')
27 self.import_feed(compiler.uri, 'Compiler.xml')
29 binary_arch = arch.Architecture({None: 1}, {None: 1})
30 assert str(binary_arch).startswith("<Arch")
31 s.solve('http://foo/Binary.xml', binary_arch)
33 assert s.ready
34 assert s.feeds_used == set([foo.uri]), s.feeds_used
35 assert s.selections[foo].id == 'sha1=123'
37 # Now ask for source instead
38 s.solve('http://foo/Binary.xml',
39 arch.SourceArchitecture(binary_arch),
40 command_name = 'compile')
41 assert s.ready, s.get_failure_reason()
42 assert s.feeds_used == set([foo.uri, foo_src.uri, compiler.uri]), s.feeds_used
43 assert s.selections[foo].id == 'sha1=234' # The source
44 assert s.selections[compiler].id == 'sha1=345' # A binary needed to compile it
46 assert not s.details
48 def testCommand(self):
49 s = solver.DefaultSolver(self.config)
50 binary_arch = arch.Architecture({None: 1}, {None: 1})
51 s.solve(command_dep, binary_arch)
52 command = s.selections.selections[s.selections.interface].get_command("run")
53 dep, = command.requires
54 dep_impl = s.selections.selections[dep.interface]
55 assert dep_impl.get_command("run").path == "test-gui"
57 def testDetails(self):
58 iface_cache = self.config.iface_cache
59 s = solver.DefaultSolver(self.config)
61 foo_binary_uri = 'http://foo/Binary.xml'
62 foo = iface_cache.get_interface(foo_binary_uri)
63 self.import_feed(foo_binary_uri, 'Binary.xml')
64 foo_src = iface_cache.get_interface('http://foo/Source.xml')
65 self.import_feed(foo_src.uri, 'Source.xml')
66 compiler = iface_cache.get_interface('http://foo/Compiler.xml')
67 self.import_feed(compiler.uri, 'Compiler.xml')
69 r = Requirements('http://foo/Binary.xml')
70 r.source = True
71 r.command = 'compile'
73 s.record_details = True
74 s.solve_for(r)
75 assert s.ready, s.get_failure_reason()
77 foo_bin_impls = iface_cache.get_feed(foo_binary_uri).implementations
78 foo_src_impls = iface_cache.get_feed(foo_src.uri).implementations
79 foo_impls = iface_cache.get_feed(foo.uri).implementations
80 compiler_impls = iface_cache.get_feed(compiler.uri).implementations
82 assert len(s.details) == 2
83 self.assertEqual([
84 (foo_src_impls['impossible'], None),
85 (foo_src_impls['sha1=234'], None),
86 (foo_impls['sha1=123'], 'Not source code'),
87 (foo_src_impls['old'], None),
88 ], sorted(s.details[foo]))
89 self.assertEqual([
90 (compiler_impls['sha1=999'], None),
91 (compiler_impls['sha1=345'], None),
92 (compiler_impls['sha1=678'], None),
93 ], s.details[compiler])
95 def justify(uri, impl, expected):
96 iface = iface_cache.get_interface(uri)
97 e = s.justify_decision(r, iface, impl)
98 self.assertEqual(expected, e)
100 justify(foo_binary_uri, foo_bin_impls["sha1=123"],
101 'Binary 1.0 cannot be used (regardless of other components): Not source code')
102 justify(foo_binary_uri, foo_src_impls["sha1=234"],
103 'Binary 1.0 was selected as the preferred version.')
104 justify(foo_binary_uri, foo_src_impls["old"],
105 'Binary 0.1 is ranked lower than 1.0: newer versions are preferred')
106 justify(foo_binary_uri, foo_src_impls["impossible"],
107 '''There is no possible selection using Binary 3.\nCan't find all required implementations:\n- <Interface http://foo/Binary.xml> -> impossible\n- <Interface http://foo/Compiler.xml> -> None''')
108 justify(compiler.uri, compiler_impls["sha1=999"],
109 '''Compiler 5 is selectable, but using it would produce a less optimal solution overall.\n\nThe changes would be:\n\nhttp://foo/Binary.xml: 1.0 to 0.1''')
111 def testRecursive(self):
112 iface_cache = self.config.iface_cache
113 s = solver.DefaultSolver(self.config)
115 foo = iface_cache.get_interface('http://foo/Recursive.xml')
116 self.import_feed(foo.uri, 'Recursive.xml')
118 binary_arch = arch.Architecture({None: 1}, {None: 1})
119 s.record_details = True
120 s.solve('http://foo/Recursive.xml', binary_arch)
121 assert s.ready
123 foo_impls = iface_cache.get_feed(foo.uri).implementations
125 assert len(s.details) == 1
126 assert s.details[foo] == [(foo_impls['sha1=abc'], None)]
128 def testMultiArch(self):
129 iface_cache = self.config.iface_cache
130 s = solver.DefaultSolver(self.config)
132 foo = iface_cache.get_interface('http://foo/MultiArch.xml')
133 self.import_feed(foo.uri, 'MultiArch.xml')
134 lib = iface_cache.get_interface('http://foo/MultiArchLib.xml')
135 self.import_feed(lib.uri, 'MultiArchLib.xml')
137 # On an i686 system we can only use the i486 implementation
139 binary_arch = arch.get_architecture('Linux', 'i686')
140 s.solve('http://foo/MultiArch.xml', binary_arch)
141 assert s.ready
142 assert s.selections[foo].machine == 'i486'
143 assert s.selections[lib].machine == 'i486'
145 # On an 64 bit system we could use either, but we prefer the 64
146 # bit implementation. The i486 version of the library is newer,
147 # but we must pick one that is compatible with the main binary.
149 binary_arch = arch.get_architecture('Linux', 'x86_64')
150 s.solve('http://foo/MultiArch.xml', binary_arch)
151 assert s.ready
152 assert s.selections[foo].machine == 'x86_64'
153 assert s.selections[lib].machine == 'x86_64'
155 def testArch(self):
156 host_arch = arch.get_host_architecture()
157 host_arch2 = arch.get_architecture(None, None)
158 self.assertEqual(host_arch.os_ranks, host_arch2.os_ranks)
159 self.assertEqual(host_arch.machine_ranks, host_arch2.machine_ranks)
161 other = arch.get_architecture('FooBar', 'i486')
162 self.assertEqual(3, len(other.os_ranks))
164 assert 'POSIX' in other.os_ranks
165 assert 'FooBar' in other.os_ranks
166 assert None in other.os_ranks
167 assert 'i486' in other.machine_ranks
168 assert 'ppc' not in other.machine_ranks
170 win = arch.get_architecture('Windows', 'i486')
171 self.assertEqual(2, len(win.os_ranks))
172 assert 'POSIX' not in win.os_ranks
174 def testArchFor(self):
175 s = solver.DefaultSolver(self.config)
176 r = Requirements('http://foo/Binary.xml')
178 r.cpu = 'i386'
179 bin_arch = s.get_arch_for(r)
180 self.assertEqual({'i386': 0, None: 1}, bin_arch.machine_ranks)
182 r.source = True
183 src_arch = s.get_arch_for(r)
184 self.assertEqual({'src': 1}, src_arch.machine_ranks)
186 child = self.config.iface_cache.get_interface('http://foo/Dep.xml')
187 arch = s.get_arch_for(r, child)
188 self.assertEqual(arch.machine_ranks, bin_arch.machine_ranks)
190 child = self.config.iface_cache.get_interface(r.interface_uri)
191 arch = s.get_arch_for(r, child)
192 self.assertEqual(arch.machine_ranks, src_arch.machine_ranks)
194 def testRanking(self):
195 iface_cache = self.config.iface_cache
196 s = solver.DefaultSolver(self.config)
197 ranking = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'Ranking.xml')
198 iface = iface_cache.get_interface(ranking)
200 binary_arch = arch.get_architecture('Linux', 'x86_64')
201 selected = []
202 while True:
203 s.solve(ranking, binary_arch)
204 if not s.ready:
205 break
206 impl = s.selections[iface]
207 selected.append(impl.get_version() + ' ' + impl.arch)
208 impl.arch = 'Foo-odd' # prevent reselection
209 self.assertEqual([
210 '0.2 Linux-i386', # poor arch, but newest version
211 '0.1 Linux-x86_64', # 64-bit is best match for host arch
212 '0.1 Linux-i686', '0.1 Linux-i586', '0.1 Linux-i486'], # ordering of x86 versions
213 selected)
215 def testRestricts(self):
216 iface_cache = self.config.iface_cache
217 s = solver.DefaultSolver(self.config)
218 uri = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'Conflicts.xml')
219 versions = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'Versions.xml')
220 iface = iface_cache.get_interface(uri)
222 r = Requirements(uri)
224 # Selects 0.2 as the highest version, applying the restriction to versions < 4.
225 s.solve_for(r)
226 assert s.ready
227 self.assertEqual("0.2", s.selections.selections[uri].version)
228 self.assertEqual("3", s.selections.selections[versions].version)
230 s.extra_restrictions[iface] = [model.VersionRestriction(model.parse_version('0.1'))]
231 s.solve_for(r)
232 assert s.ready
233 self.assertEqual("0.1", s.selections.selections[uri].version)
234 self.assertEqual(None, s.selections.selections.get(versions, None))
236 s.extra_restrictions[iface] = [model.VersionRestriction(model.parse_version('0.3'))]
237 s.solve_for(r)
238 assert not s.ready
240 def testLangs(self):
241 iface_cache = self.config.iface_cache
242 try:
243 locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
245 s = solver.DefaultSolver(self.config)
246 iface = iface_cache.get_interface('http://foo/Langs.xml')
247 self.import_feed(iface.uri, 'Langs.xml')
249 # 1 is the oldest, but the only one in our language
250 binary_arch = arch.get_architecture(None, 'arch_1')
251 s.solve('http://foo/Langs.xml', binary_arch)
252 assert s.ready
253 self.assertEqual('sha1=1', s.selections[iface].id)
255 # 6 is the newest, and close enough, even though not
256 # quite the right locale
257 binary_arch = arch.get_architecture(None, 'arch_2')
258 s.solve('http://foo/Langs.xml', binary_arch)
259 assert s.ready
260 self.assertEqual('sha1=6', s.selections[iface].id)
262 # 9 is the newest, although 7 is a closer match
263 binary_arch = arch.get_architecture(None, 'arch_3')
264 s.solve('http://foo/Langs.xml', binary_arch)
265 assert s.ready
266 self.assertEqual('sha1=9', s.selections[iface].id)
268 # 11 is the newest we understand
269 binary_arch = arch.get_architecture(None, 'arch_4')
270 s.solve('http://foo/Langs.xml', binary_arch)
271 assert s.ready
272 self.assertEqual('sha1=11', s.selections[iface].id)
274 # 13 is the newest we understand
275 binary_arch = arch.get_architecture(None, 'arch_5')
276 s.solve('http://foo/Langs.xml', binary_arch)
277 assert s.ready
278 self.assertEqual('sha1=13', s.selections[iface].id)
280 def check(target_arch, langs, expected):
281 s.langs = langs
282 binary_arch = arch.get_architecture(None, target_arch)
283 s.solve('http://foo/Langs.xml', binary_arch)
284 assert s.ready
285 self.assertEqual(expected, s.selections[iface].id)
287 # We don't understand any, so pick the newest
288 check('arch_2', ['es_ES'], 'sha1=6')
290 # These two have the same version number. Choose the
291 # one most appropriate to our country
292 check('arch_6', ['zh_CN'], 'sha1=15')
293 check('arch_6', ['zh_TW'], 'sha1=16')
295 # Same, but one doesn't have a country code
296 check('arch_7', ['bn'], 'sha1=17')
297 check('arch_7', ['bn_IN'], 'sha1=18')
298 finally:
299 locale.setlocale(locale.LC_ALL, '')
301 def testDecideBug(self):
302 s = solver.DefaultSolver(self.config)
303 watch_xml = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'watchdog.xml')
304 s.solve(watch_xml, arch.get_architecture(None, None), command_name = 'test')
306 def testRecommendBug(self):
307 s = solver.DefaultSolver(self.config)
308 optional_missing_xml = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'OptionalMissing.xml')
309 s.solve(optional_missing_xml, arch.get_architecture(None, None), command_name = None)
311 def testFeedBug(self):
312 self.import_feed('http://foo/Build.xml', 'Build.xml')
313 self.import_feed('http://foo/Compiler.xml', 'Compiler.xml')
314 self.import_feed('http://foo/Compiler-new.xml', 'Compiler-new.xml')
315 s = solver.DefaultSolver(self.config)
316 s.solve('http://foo/Build.xml', arch.get_architecture(None, None))
317 assert s.ready, s.get_failure_reason()
318 assert s.selections
320 if __name__ == '__main__':
321 unittest.main()