Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1 | """Tests for packaging.depgraph """ |
| 2 | import io |
| 3 | import os |
| 4 | import re |
| 5 | import sys |
| 6 | import packaging.database |
| 7 | from packaging import depgraph |
| 8 | |
| 9 | from packaging.tests import unittest, support |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 10 | from packaging.tests.support import requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 11 | |
| 12 | |
| 13 | class DepGraphTestCase(support.LoggingCatcher, |
| 14 | unittest.TestCase): |
| 15 | |
| 16 | DISTROS_DIST = ('choxie', 'grammar', 'towel-stuff') |
| 17 | DISTROS_EGG = ('bacon', 'banana', 'strawberry', 'cheese') |
| 18 | BAD_EGGS = ('nut',) |
| 19 | |
| 20 | EDGE = re.compile( |
| 21 | r'"(?P<from>.*)" -> "(?P<to>.*)" \[label="(?P<label>.*)"\]') |
| 22 | |
| 23 | def checkLists(self, l1, l2): |
| 24 | """ Compare two lists without taking the order into consideration """ |
| 25 | self.assertListEqual(sorted(l1), sorted(l2)) |
| 26 | |
| 27 | def setUp(self): |
| 28 | super(DepGraphTestCase, self).setUp() |
| 29 | path = os.path.join(os.path.dirname(__file__), 'fake_dists') |
| 30 | path = os.path.abspath(path) |
| 31 | sys.path.insert(0, path) |
| 32 | self.addCleanup(sys.path.remove, path) |
| 33 | self.addCleanup(packaging.database.enable_cache) |
| 34 | packaging.database.disable_cache() |
| 35 | |
| 36 | def test_generate_graph(self): |
| 37 | dists = [] |
| 38 | for name in self.DISTROS_DIST: |
| 39 | dist = packaging.database.get_distribution(name) |
| 40 | self.assertNotEqual(dist, None) |
| 41 | dists.append(dist) |
| 42 | |
| 43 | choxie, grammar, towel = dists |
| 44 | |
| 45 | graph = depgraph.generate_graph(dists) |
| 46 | |
| 47 | deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] |
| 48 | self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) |
| 49 | self.assertIn(choxie, graph.reverse_list[towel]) |
| 50 | self.checkLists(graph.missing[choxie], ['nut']) |
| 51 | |
| 52 | deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] |
| 53 | self.checkLists([], deps) |
| 54 | self.checkLists(graph.missing[grammar], ['truffles (>=1.2)']) |
| 55 | |
| 56 | deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] |
| 57 | self.checkLists([], deps) |
| 58 | self.checkLists(graph.missing[towel], ['bacon (<=0.2)']) |
| 59 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 60 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 61 | def test_generate_graph_egg(self): |
| 62 | dists = [] |
| 63 | for name in self.DISTROS_DIST + self.DISTROS_EGG: |
| 64 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 65 | self.assertNotEqual(dist, None) |
| 66 | dists.append(dist) |
| 67 | |
| 68 | choxie, grammar, towel, bacon, banana, strawberry, cheese = dists |
| 69 | |
| 70 | graph = depgraph.generate_graph(dists) |
| 71 | |
| 72 | deps = [(x.name, y) for x, y in graph.adjacency_list[choxie]] |
| 73 | self.checkLists([('towel-stuff', 'towel-stuff (0.1)')], deps) |
| 74 | self.assertIn(choxie, graph.reverse_list[towel]) |
| 75 | self.checkLists(graph.missing[choxie], ['nut']) |
| 76 | |
| 77 | deps = [(x.name, y) for x, y in graph.adjacency_list[grammar]] |
| 78 | self.checkLists([('bacon', 'truffles (>=1.2)')], deps) |
| 79 | self.checkLists(graph.missing[grammar], []) |
| 80 | self.assertIn(grammar, graph.reverse_list[bacon]) |
| 81 | |
| 82 | deps = [(x.name, y) for x, y in graph.adjacency_list[towel]] |
| 83 | self.checkLists([('bacon', 'bacon (<=0.2)')], deps) |
| 84 | self.checkLists(graph.missing[towel], []) |
| 85 | self.assertIn(towel, graph.reverse_list[bacon]) |
| 86 | |
| 87 | deps = [(x.name, y) for x, y in graph.adjacency_list[bacon]] |
| 88 | self.checkLists([], deps) |
| 89 | self.checkLists(graph.missing[bacon], []) |
| 90 | |
| 91 | deps = [(x.name, y) for x, y in graph.adjacency_list[banana]] |
| 92 | self.checkLists([('strawberry', 'strawberry (>=0.5)')], deps) |
| 93 | self.checkLists(graph.missing[banana], []) |
| 94 | self.assertIn(banana, graph.reverse_list[strawberry]) |
| 95 | |
| 96 | deps = [(x.name, y) for x, y in graph.adjacency_list[strawberry]] |
| 97 | self.checkLists([], deps) |
| 98 | self.checkLists(graph.missing[strawberry], []) |
| 99 | |
| 100 | deps = [(x.name, y) for x, y in graph.adjacency_list[cheese]] |
| 101 | self.checkLists([], deps) |
| 102 | self.checkLists(graph.missing[cheese], []) |
| 103 | |
| 104 | def test_dependent_dists(self): |
| 105 | dists = [] |
| 106 | for name in self.DISTROS_DIST: |
| 107 | dist = packaging.database.get_distribution(name) |
| 108 | self.assertNotEqual(dist, None) |
| 109 | dists.append(dist) |
| 110 | |
| 111 | choxie, grammar, towel = dists |
| 112 | |
| 113 | deps = [d.name for d in depgraph.dependent_dists(dists, choxie)] |
| 114 | self.checkLists([], deps) |
| 115 | |
| 116 | deps = [d.name for d in depgraph.dependent_dists(dists, grammar)] |
| 117 | self.checkLists([], deps) |
| 118 | |
| 119 | deps = [d.name for d in depgraph.dependent_dists(dists, towel)] |
| 120 | self.checkLists(['choxie'], deps) |
| 121 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 122 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 123 | def test_dependent_dists_egg(self): |
| 124 | dists = [] |
| 125 | for name in self.DISTROS_DIST + self.DISTROS_EGG: |
| 126 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 127 | self.assertNotEqual(dist, None) |
| 128 | dists.append(dist) |
| 129 | |
| 130 | choxie, grammar, towel, bacon, banana, strawberry, cheese = dists |
| 131 | |
| 132 | deps = [d.name for d in depgraph.dependent_dists(dists, choxie)] |
| 133 | self.checkLists([], deps) |
| 134 | |
| 135 | deps = [d.name for d in depgraph.dependent_dists(dists, grammar)] |
| 136 | self.checkLists([], deps) |
| 137 | |
| 138 | deps = [d.name for d in depgraph.dependent_dists(dists, towel)] |
| 139 | self.checkLists(['choxie'], deps) |
| 140 | |
| 141 | deps = [d.name for d in depgraph.dependent_dists(dists, bacon)] |
| 142 | self.checkLists(['choxie', 'towel-stuff', 'grammar'], deps) |
| 143 | |
| 144 | deps = [d.name for d in depgraph.dependent_dists(dists, strawberry)] |
| 145 | self.checkLists(['banana'], deps) |
| 146 | |
| 147 | deps = [d.name for d in depgraph.dependent_dists(dists, cheese)] |
| 148 | self.checkLists([], deps) |
| 149 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 150 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 151 | def test_graph_to_dot(self): |
| 152 | expected = ( |
| 153 | ('towel-stuff', 'bacon', 'bacon (<=0.2)'), |
| 154 | ('grammar', 'bacon', 'truffles (>=1.2)'), |
| 155 | ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), |
| 156 | ('banana', 'strawberry', 'strawberry (>=0.5)'), |
| 157 | ) |
| 158 | |
| 159 | dists = [] |
| 160 | for name in self.DISTROS_DIST + self.DISTROS_EGG: |
| 161 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 162 | self.assertNotEqual(dist, None) |
| 163 | dists.append(dist) |
| 164 | |
| 165 | graph = depgraph.generate_graph(dists) |
| 166 | buf = io.StringIO() |
| 167 | depgraph.graph_to_dot(graph, buf) |
| 168 | buf.seek(0) |
| 169 | matches = [] |
| 170 | lines = buf.readlines() |
| 171 | for line in lines[1:-1]: # skip the first and the last lines |
| 172 | if line[-1] == '\n': |
| 173 | line = line[:-1] |
| 174 | match = self.EDGE.match(line.strip()) |
| 175 | self.assertIsNot(match, None) |
| 176 | matches.append(match.groups()) |
| 177 | |
| 178 | self.checkLists(matches, expected) |
| 179 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 180 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 181 | def test_graph_disconnected_to_dot(self): |
| 182 | dependencies_expected = ( |
| 183 | ('towel-stuff', 'bacon', 'bacon (<=0.2)'), |
| 184 | ('grammar', 'bacon', 'truffles (>=1.2)'), |
| 185 | ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), |
| 186 | ('banana', 'strawberry', 'strawberry (>=0.5)'), |
| 187 | ) |
| 188 | disconnected_expected = ('cheese', 'bacon', 'strawberry') |
| 189 | |
| 190 | dists = [] |
| 191 | for name in self.DISTROS_DIST + self.DISTROS_EGG: |
| 192 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 193 | self.assertNotEqual(dist, None) |
| 194 | dists.append(dist) |
| 195 | |
| 196 | graph = depgraph.generate_graph(dists) |
| 197 | buf = io.StringIO() |
| 198 | depgraph.graph_to_dot(graph, buf, skip_disconnected=False) |
| 199 | buf.seek(0) |
| 200 | lines = buf.readlines() |
| 201 | |
| 202 | dependencies_lines = [] |
| 203 | disconnected_lines = [] |
| 204 | |
| 205 | # First sort output lines into dependencies and disconnected lines. |
| 206 | # We also skip the attribute lines, and don't include the "{" and "}" |
| 207 | # lines. |
| 208 | disconnected_active = False |
| 209 | for line in lines[1:-1]: # Skip first and last line |
| 210 | if line.startswith('subgraph disconnected'): |
| 211 | disconnected_active = True |
| 212 | continue |
| 213 | if line.startswith('}') and disconnected_active: |
| 214 | disconnected_active = False |
| 215 | continue |
| 216 | |
| 217 | if disconnected_active: |
| 218 | # Skip the 'label = "Disconnected"', etc. attribute lines. |
| 219 | if ' = ' not in line: |
| 220 | disconnected_lines.append(line) |
| 221 | else: |
| 222 | dependencies_lines.append(line) |
| 223 | |
| 224 | dependencies_matches = [] |
| 225 | for line in dependencies_lines: |
| 226 | if line[-1] == '\n': |
| 227 | line = line[:-1] |
| 228 | match = self.EDGE.match(line.strip()) |
| 229 | self.assertIsNot(match, None) |
| 230 | dependencies_matches.append(match.groups()) |
| 231 | |
| 232 | disconnected_matches = [] |
| 233 | for line in disconnected_lines: |
| 234 | if line[-1] == '\n': |
| 235 | line = line[:-1] |
| 236 | line = line.strip('"') |
| 237 | disconnected_matches.append(line) |
| 238 | |
| 239 | self.checkLists(dependencies_matches, dependencies_expected) |
| 240 | self.checkLists(disconnected_matches, disconnected_expected) |
| 241 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 242 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 243 | def test_graph_bad_version_to_dot(self): |
| 244 | expected = ( |
| 245 | ('towel-stuff', 'bacon', 'bacon (<=0.2)'), |
| 246 | ('grammar', 'bacon', 'truffles (>=1.2)'), |
| 247 | ('choxie', 'towel-stuff', 'towel-stuff (0.1)'), |
| 248 | ('banana', 'strawberry', 'strawberry (>=0.5)'), |
| 249 | ) |
| 250 | |
| 251 | dists = [] |
| 252 | for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: |
| 253 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 254 | self.assertNotEqual(dist, None) |
| 255 | dists.append(dist) |
| 256 | |
| 257 | graph = depgraph.generate_graph(dists) |
| 258 | buf = io.StringIO() |
| 259 | depgraph.graph_to_dot(graph, buf) |
| 260 | buf.seek(0) |
| 261 | matches = [] |
| 262 | lines = buf.readlines() |
| 263 | for line in lines[1:-1]: # skip the first and the last lines |
| 264 | if line[-1] == '\n': |
| 265 | line = line[:-1] |
| 266 | match = self.EDGE.match(line.strip()) |
| 267 | self.assertIsNot(match, None) |
| 268 | matches.append(match.groups()) |
| 269 | |
| 270 | self.checkLists(matches, expected) |
| 271 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 272 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 273 | def test_repr(self): |
| 274 | dists = [] |
| 275 | for name in self.DISTROS_DIST + self.DISTROS_EGG + self.BAD_EGGS: |
| 276 | dist = packaging.database.get_distribution(name, use_egg_info=True) |
| 277 | self.assertNotEqual(dist, None) |
| 278 | dists.append(dist) |
| 279 | |
| 280 | graph = depgraph.generate_graph(dists) |
| 281 | self.assertTrue(repr(graph)) |
| 282 | |
Ezio Melotti | cad648c | 2011-05-19 21:25:10 +0300 | [diff] [blame] | 283 | @requires_zlib |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 284 | def test_main(self): |
| 285 | tempout = io.StringIO() |
| 286 | old = sys.stdout |
| 287 | sys.stdout = tempout |
| 288 | oldargv = sys.argv[:] |
| 289 | sys.argv[:] = ['script.py'] |
| 290 | try: |
| 291 | try: |
| 292 | depgraph.main() |
| 293 | except SystemExit: |
| 294 | pass |
| 295 | finally: |
| 296 | sys.stdout = old |
| 297 | sys.argv[:] = oldargv |
| 298 | |
| 299 | # checks what main did XXX could do more here |
| 300 | tempout.seek(0) |
| 301 | res = tempout.read() |
| 302 | self.assertIn('towel', res) |
| 303 | |
| 304 | |
| 305 | def test_suite(): |
| 306 | return unittest.makeSuite(DepGraphTestCase) |
| 307 | |
| 308 | if __name__ == "__main__": |
| 309 | unittest.main(defaultTest="test_suite") |