| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | """ |
|---|
| 3 | Tests for allmydata.immutable.happiness_upload and |
|---|
| 4 | allmydata.util.happinessutil. |
|---|
| 5 | |
|---|
| 6 | Ported to Python 3. |
|---|
| 7 | """ |
|---|
| 8 | |
|---|
| 9 | from twisted.trial import unittest |
|---|
| 10 | from hypothesis import given |
|---|
| 11 | from hypothesis.strategies import text, sets |
|---|
| 12 | |
|---|
| 13 | from allmydata.immutable import happiness_upload |
|---|
| 14 | from allmydata.util.happinessutil import servers_of_happiness, \ |
|---|
| 15 | shares_by_server, merge_servers |
|---|
| 16 | from allmydata.test.common import ShouldFailMixin |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | class HappinessUploadUtils(unittest.TestCase): |
|---|
| 20 | """ |
|---|
| 21 | test-cases for happiness_upload utility functions augmenting_path_for and |
|---|
| 22 | residual_network. |
|---|
| 23 | """ |
|---|
| 24 | |
|---|
| 25 | def test_residual_0(self): |
|---|
| 26 | graph = happiness_upload._servermap_flow_graph( |
|---|
| 27 | ['peer0'], |
|---|
| 28 | ['share0'], |
|---|
| 29 | servermap={ |
|---|
| 30 | 'peer0': ['share0'], |
|---|
| 31 | } |
|---|
| 32 | ) |
|---|
| 33 | flow = [[0 for _ in graph] for _ in graph] |
|---|
| 34 | |
|---|
| 35 | residual, capacity = happiness_upload.residual_network(graph, flow) |
|---|
| 36 | |
|---|
| 37 | # XXX no idea if these are right; hand-verify |
|---|
| 38 | self.assertEqual(residual, [[1], [2], [3], []]) |
|---|
| 39 | self.assertEqual(capacity, [[0, 1, 0, 0], [-1, 0, 1, 0], [0, -1, 0, 1], [0, 0, -1, 0]]) |
|---|
| 40 | |
|---|
| 41 | def test_trivial_maximum_graph(self): |
|---|
| 42 | self.assertEqual( |
|---|
| 43 | {}, |
|---|
| 44 | happiness_upload._compute_maximum_graph([], {}) |
|---|
| 45 | ) |
|---|
| 46 | |
|---|
| 47 | def test_trivial_flow_graph(self): |
|---|
| 48 | self.assertEqual( |
|---|
| 49 | [], |
|---|
| 50 | happiness_upload._servermap_flow_graph(set(), set(), {}) |
|---|
| 51 | ) |
|---|
| 52 | |
|---|
| 53 | |
|---|
| 54 | class Happiness(unittest.TestCase): |
|---|
| 55 | |
|---|
| 56 | def test_placement_simple(self): |
|---|
| 57 | |
|---|
| 58 | shares = {'share0', 'share1', 'share2'} |
|---|
| 59 | peers = {'peer0', 'peer1'} |
|---|
| 60 | readonly_peers = {'peer0'} |
|---|
| 61 | peers_to_shares = { |
|---|
| 62 | 'peer0': {'share2'}, |
|---|
| 63 | 'peer1': [], |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 67 | |
|---|
| 68 | self.assertEqual( |
|---|
| 69 | places, |
|---|
| 70 | { |
|---|
| 71 | 'share0': 'peer1', |
|---|
| 72 | 'share1': 'peer1', |
|---|
| 73 | 'share2': 'peer0', |
|---|
| 74 | } |
|---|
| 75 | ) |
|---|
| 76 | |
|---|
| 77 | def test_placement_1(self): |
|---|
| 78 | |
|---|
| 79 | shares = { |
|---|
| 80 | 'share0', 'share1', 'share2', |
|---|
| 81 | 'share3', 'share4', 'share5', |
|---|
| 82 | 'share6', 'share7', 'share8', |
|---|
| 83 | 'share9', |
|---|
| 84 | } |
|---|
| 85 | peers = { |
|---|
| 86 | 'peer0', 'peer1', 'peer2', 'peer3', |
|---|
| 87 | 'peer4', 'peer5', 'peer6', 'peer7', |
|---|
| 88 | 'peer8', 'peer9', 'peerA', 'peerB', |
|---|
| 89 | } |
|---|
| 90 | readonly_peers = {'peer0', 'peer1', 'peer2', 'peer3'} |
|---|
| 91 | peers_to_shares = { |
|---|
| 92 | 'peer0': {'share0'}, |
|---|
| 93 | 'peer1': {'share1'}, |
|---|
| 94 | 'peer2': {'share2'}, |
|---|
| 95 | 'peer3': {'share3'}, |
|---|
| 96 | 'peer4': {'share4'}, |
|---|
| 97 | 'peer5': {'share5'}, |
|---|
| 98 | 'peer6': {'share6'}, |
|---|
| 99 | 'peer7': {'share7'}, |
|---|
| 100 | 'peer8': {'share8'}, |
|---|
| 101 | 'peer9': {'share9'}, |
|---|
| 102 | 'peerA': set(), |
|---|
| 103 | 'peerB': set(), |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 107 | |
|---|
| 108 | # actually many valid answers for this, so long as peer's 0, |
|---|
| 109 | # 1, 2, 3 all have share 0, 1, 2 3. |
|---|
| 110 | |
|---|
| 111 | # share N maps to peer N |
|---|
| 112 | # i.e. this says that share0 should be on peer0, share1 should |
|---|
| 113 | # be on peer1, etc. |
|---|
| 114 | expected = { |
|---|
| 115 | 'share{}'.format(i): 'peer{}'.format(i) |
|---|
| 116 | for i in range(10) |
|---|
| 117 | } |
|---|
| 118 | self.assertEqual(expected, places) |
|---|
| 119 | |
|---|
| 120 | def test_unhappy(self): |
|---|
| 121 | shares = { |
|---|
| 122 | 'share1', 'share2', 'share3', 'share4', 'share5', |
|---|
| 123 | } |
|---|
| 124 | peers = { |
|---|
| 125 | 'peer1', 'peer2', 'peer3', 'peer4', |
|---|
| 126 | } |
|---|
| 127 | readonly_peers = set() |
|---|
| 128 | peers_to_shares = {} |
|---|
| 129 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 130 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 131 | self.assertEqual(4, happiness) |
|---|
| 132 | |
|---|
| 133 | def test_hypothesis0(self): |
|---|
| 134 | peers={u'0', u'00'} |
|---|
| 135 | shares={u'0', u'1'} |
|---|
| 136 | readonly_peers = set() |
|---|
| 137 | peers_to_shares = dict() |
|---|
| 138 | |
|---|
| 139 | #h = happiness_upload.HappinessUpload(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 140 | #places = h.generate_mappings() |
|---|
| 141 | #happiness = h.happiness() |
|---|
| 142 | |
|---|
| 143 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 144 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 145 | |
|---|
| 146 | self.assertEqual(2, happiness) |
|---|
| 147 | |
|---|
| 148 | def test_100(self): |
|---|
| 149 | peers = set(['peer{}'.format(x) for x in range(100)]) |
|---|
| 150 | shares = set(['share{}'.format(x) for x in range(100)]) |
|---|
| 151 | readonly_peers = set() |
|---|
| 152 | peers_to_shares = dict() |
|---|
| 153 | |
|---|
| 154 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 155 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 156 | |
|---|
| 157 | self.assertEqual(100, happiness) |
|---|
| 158 | |
|---|
| 159 | def test_redistribute(self): |
|---|
| 160 | """ |
|---|
| 161 | with existing shares 0, 3 on a single servers we can achieve |
|---|
| 162 | higher happiness by moving one of those shares to a new server |
|---|
| 163 | """ |
|---|
| 164 | peers = {'a', 'b', 'c', 'd'} |
|---|
| 165 | shares = {'0', '1', '2', '3'} |
|---|
| 166 | readonly_peers = set() |
|---|
| 167 | peers_to_shares = { |
|---|
| 168 | 'a': set(['0']), |
|---|
| 169 | 'b': set(['1']), |
|---|
| 170 | 'c': set(['2', '3']), |
|---|
| 171 | } |
|---|
| 172 | # we can achieve more happiness by moving "2" or "3" to server "d" |
|---|
| 173 | |
|---|
| 174 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 175 | #print("places %s" % places) |
|---|
| 176 | #places = happiness_upload.slow_share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 177 | #print("places %s" % places) |
|---|
| 178 | |
|---|
| 179 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 180 | self.assertEqual(4, happiness) |
|---|
| 181 | |
|---|
| 182 | def test_calc_happy(self): |
|---|
| 183 | # share -> server |
|---|
| 184 | share_placements = { |
|---|
| 185 | 0: "\x0e\xd6\xb3>\xd6\x85\x9d\x94')'\xf03:R\x88\xf1\x04\x1b\xa4", |
|---|
| 186 | 1: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 187 | 2: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 188 | 3: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 189 | 4: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 190 | 5: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 191 | 6: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 192 | 7: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 193 | 8: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 194 | 9: '\xb9\xa3N\x80u\x9c_\xf7\x97FSS\xa7\xbd\x02\xf9f$:\t', |
|---|
| 195 | } |
|---|
| 196 | happy = happiness_upload.calculate_happiness(share_placements) |
|---|
| 197 | self.assertEqual(2, happy) |
|---|
| 198 | |
|---|
| 199 | def test_hypothesis_0(self): |
|---|
| 200 | """ |
|---|
| 201 | an error-case Hypothesis found |
|---|
| 202 | """ |
|---|
| 203 | peers={u'0'} |
|---|
| 204 | shares={u'0', u'1'} |
|---|
| 205 | |
|---|
| 206 | places = happiness_upload.share_placement(peers, set(), shares, {}) |
|---|
| 207 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 208 | |
|---|
| 209 | assert set(places.values()).issubset(peers) |
|---|
| 210 | assert happiness == min(len(peers), len(shares)) |
|---|
| 211 | |
|---|
| 212 | def test_hypothesis_1(self): |
|---|
| 213 | """ |
|---|
| 214 | an error-case Hypothesis found |
|---|
| 215 | """ |
|---|
| 216 | peers = {u'0', u'1', u'2', u'3'} |
|---|
| 217 | shares = {u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8'} |
|---|
| 218 | |
|---|
| 219 | places = happiness_upload.share_placement(peers, set(), shares, {}) |
|---|
| 220 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 221 | |
|---|
| 222 | assert set(places.values()).issubset(peers) |
|---|
| 223 | assert happiness == min(len(peers), len(shares)) |
|---|
| 224 | |
|---|
| 225 | def test_everything_broken(self): |
|---|
| 226 | peers = set() |
|---|
| 227 | shares = {u'0', u'1', u'2', u'3'} |
|---|
| 228 | |
|---|
| 229 | places = happiness_upload.share_placement(peers, set(), shares, {}) |
|---|
| 230 | self.assertEqual(places, dict()) |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | class PlacementTests(unittest.TestCase): |
|---|
| 234 | |
|---|
| 235 | @given( |
|---|
| 236 | sets(elements=text(min_size=1, max_size=30), min_size=4, max_size=4), |
|---|
| 237 | sets(elements=text(min_size=1, max_size=30), min_size=4), |
|---|
| 238 | ) |
|---|
| 239 | def test_hypothesis_unhappy(self, peers, shares): |
|---|
| 240 | """ |
|---|
| 241 | similar to test_unhappy we test that the resulting happiness is |
|---|
| 242 | always 4 since the size of peers is 4. |
|---|
| 243 | """ |
|---|
| 244 | # https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.sets |
|---|
| 245 | # hypothesis.strategies.sets(elements=None, min_size=None, average_size=None, max_size=None)[source] |
|---|
| 246 | readonly_peers = set() |
|---|
| 247 | peers_to_shares = {} |
|---|
| 248 | places = happiness_upload.share_placement(peers, readonly_peers, shares, peers_to_shares) |
|---|
| 249 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 250 | assert set(places.keys()) == shares |
|---|
| 251 | assert happiness == 4 |
|---|
| 252 | |
|---|
| 253 | @given( |
|---|
| 254 | sets(elements=text(min_size=1, max_size=30), min_size=1, max_size=10), |
|---|
| 255 | # can we make a readonly_peers that's a subset of ^ |
|---|
| 256 | sets(elements=text(min_size=1, max_size=30), min_size=1, max_size=20), |
|---|
| 257 | ) |
|---|
| 258 | def test_more_hypothesis(self, peers, shares): |
|---|
| 259 | """ |
|---|
| 260 | similar to test_unhappy we test that the resulting happiness is |
|---|
| 261 | always either the number of peers or the number of shares |
|---|
| 262 | whichever is smaller. |
|---|
| 263 | """ |
|---|
| 264 | # https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.sets |
|---|
| 265 | # hypothesis.strategies.sets(elements=None, min_size=None, average_size=None, max_size=None)[source] |
|---|
| 266 | # XXX would be nice to paramaterize these by hypothesis too |
|---|
| 267 | readonly_peers = set() |
|---|
| 268 | peers_to_shares = {} |
|---|
| 269 | |
|---|
| 270 | places = happiness_upload.share_placement(peers, readonly_peers, set(list(shares)), peers_to_shares) |
|---|
| 271 | happiness = happiness_upload.calculate_happiness(places) |
|---|
| 272 | |
|---|
| 273 | # every share should get placed |
|---|
| 274 | assert set(places.keys()) == shares |
|---|
| 275 | |
|---|
| 276 | # we should only use peers that exist |
|---|
| 277 | assert set(places.values()).issubset(peers) |
|---|
| 278 | |
|---|
| 279 | # if we have more shares than peers, happiness is at most # of |
|---|
| 280 | # peers; if we have fewer shares than peers happiness is capped at |
|---|
| 281 | # # of peers. |
|---|
| 282 | assert happiness == min(len(peers), len(shares)) |
|---|
| 283 | |
|---|
| 284 | |
|---|
| 285 | class FakeServerTracker: |
|---|
| 286 | def __init__(self, serverid, buckets): |
|---|
| 287 | self._serverid = serverid |
|---|
| 288 | self.buckets = buckets |
|---|
| 289 | def get_serverid(self): |
|---|
| 290 | return self._serverid |
|---|
| 291 | |
|---|
| 292 | |
|---|
| 293 | class HappinessUtilTests(unittest.TestCase, ShouldFailMixin): |
|---|
| 294 | """Tests for happinesutil.py.""" |
|---|
| 295 | |
|---|
| 296 | def test_merge_servers(self): |
|---|
| 297 | # merge_servers merges a list of upload_servers and a dict of |
|---|
| 298 | # shareid -> serverid mappings. |
|---|
| 299 | shares = { |
|---|
| 300 | 1 : set(["server1"]), |
|---|
| 301 | 2 : set(["server2"]), |
|---|
| 302 | 3 : set(["server3"]), |
|---|
| 303 | 4 : set(["server4", "server5"]), |
|---|
| 304 | 5 : set(["server1", "server2"]), |
|---|
| 305 | } |
|---|
| 306 | # if not provided with a upload_servers argument, it should just |
|---|
| 307 | # return the first argument unchanged. |
|---|
| 308 | self.failUnlessEqual(shares, merge_servers(shares, set([]))) |
|---|
| 309 | trackers = [] |
|---|
| 310 | for (i, server) in [(i, "server%d" % i) for i in range(5, 9)]: |
|---|
| 311 | t = FakeServerTracker(server, [i]) |
|---|
| 312 | trackers.append(t) |
|---|
| 313 | expected = { |
|---|
| 314 | 1 : set(["server1"]), |
|---|
| 315 | 2 : set(["server2"]), |
|---|
| 316 | 3 : set(["server3"]), |
|---|
| 317 | 4 : set(["server4", "server5"]), |
|---|
| 318 | 5 : set(["server1", "server2", "server5"]), |
|---|
| 319 | 6 : set(["server6"]), |
|---|
| 320 | 7 : set(["server7"]), |
|---|
| 321 | 8 : set(["server8"]), |
|---|
| 322 | } |
|---|
| 323 | self.failUnlessEqual(expected, merge_servers(shares, set(trackers))) |
|---|
| 324 | shares2 = {} |
|---|
| 325 | expected = { |
|---|
| 326 | 5 : set(["server5"]), |
|---|
| 327 | 6 : set(["server6"]), |
|---|
| 328 | 7 : set(["server7"]), |
|---|
| 329 | 8 : set(["server8"]), |
|---|
| 330 | } |
|---|
| 331 | self.failUnlessEqual(expected, merge_servers(shares2, set(trackers))) |
|---|
| 332 | shares3 = {} |
|---|
| 333 | trackers = [] |
|---|
| 334 | expected = {} |
|---|
| 335 | for (i, server) in [(i, "server%d" % i) for i in range(10)]: |
|---|
| 336 | shares3[i] = set([server]) |
|---|
| 337 | t = FakeServerTracker(server, [i]) |
|---|
| 338 | trackers.append(t) |
|---|
| 339 | expected[i] = set([server]) |
|---|
| 340 | self.failUnlessEqual(expected, merge_servers(shares3, set(trackers))) |
|---|
| 341 | |
|---|
| 342 | |
|---|
| 343 | def test_servers_of_happiness_utility_function(self): |
|---|
| 344 | # These tests are concerned with the servers_of_happiness() |
|---|
| 345 | # utility function, and its underlying matching algorithm. Other |
|---|
| 346 | # aspects of the servers_of_happiness behavior are tested |
|---|
| 347 | # elsehwere These tests exist to ensure that |
|---|
| 348 | # servers_of_happiness doesn't under or overcount the happiness |
|---|
| 349 | # value for given inputs. |
|---|
| 350 | |
|---|
| 351 | # servers_of_happiness expects a dict of |
|---|
| 352 | # shnum => set(serverids) as a preexisting shares argument. |
|---|
| 353 | test1 = { |
|---|
| 354 | 1 : set(["server1"]), |
|---|
| 355 | 2 : set(["server2"]), |
|---|
| 356 | 3 : set(["server3"]), |
|---|
| 357 | 4 : set(["server4"]) |
|---|
| 358 | } |
|---|
| 359 | happy = servers_of_happiness(test1) |
|---|
| 360 | self.failUnlessEqual(4, happy) |
|---|
| 361 | test1[4] = set(["server1"]) |
|---|
| 362 | # We've added a duplicate server, so now servers_of_happiness |
|---|
| 363 | # should be 3 instead of 4. |
|---|
| 364 | happy = servers_of_happiness(test1) |
|---|
| 365 | self.failUnlessEqual(3, happy) |
|---|
| 366 | # The second argument of merge_servers should be a set of objects with |
|---|
| 367 | # serverid and buckets as attributes. In actual use, these will be |
|---|
| 368 | # ServerTracker instances, but for testing it is fine to make a |
|---|
| 369 | # FakeServerTracker whose job is to hold those instance variables to |
|---|
| 370 | # test that part. |
|---|
| 371 | trackers = [] |
|---|
| 372 | for (i, server) in [(i, "server%d" % i) for i in range(5, 9)]: |
|---|
| 373 | t = FakeServerTracker(server, [i]) |
|---|
| 374 | trackers.append(t) |
|---|
| 375 | # Recall that test1 is a server layout with servers_of_happiness |
|---|
| 376 | # = 3. Since there isn't any overlap between the shnum -> |
|---|
| 377 | # set([serverid]) correspondences in test1 and those in trackers, |
|---|
| 378 | # the result here should be 7. |
|---|
| 379 | test2 = merge_servers(test1, set(trackers)) |
|---|
| 380 | happy = servers_of_happiness(test2) |
|---|
| 381 | self.failUnlessEqual(7, happy) |
|---|
| 382 | # Now add an overlapping server to trackers. This is redundant, |
|---|
| 383 | # so it should not cause the previously reported happiness value |
|---|
| 384 | # to change. |
|---|
| 385 | t = FakeServerTracker("server1", [1]) |
|---|
| 386 | trackers.append(t) |
|---|
| 387 | test2 = merge_servers(test1, set(trackers)) |
|---|
| 388 | happy = servers_of_happiness(test2) |
|---|
| 389 | self.failUnlessEqual(7, happy) |
|---|
| 390 | test = {} |
|---|
| 391 | happy = servers_of_happiness(test) |
|---|
| 392 | self.failUnlessEqual(0, happy) |
|---|
| 393 | # Test a more substantial overlap between the trackers and the |
|---|
| 394 | # existing assignments. |
|---|
| 395 | test = { |
|---|
| 396 | 1 : set(['server1']), |
|---|
| 397 | 2 : set(['server2']), |
|---|
| 398 | 3 : set(['server3']), |
|---|
| 399 | 4 : set(['server4']), |
|---|
| 400 | } |
|---|
| 401 | trackers = [] |
|---|
| 402 | t = FakeServerTracker('server5', [4]) |
|---|
| 403 | trackers.append(t) |
|---|
| 404 | t = FakeServerTracker('server6', [3, 5]) |
|---|
| 405 | trackers.append(t) |
|---|
| 406 | # The value returned by servers_of_happiness is the size |
|---|
| 407 | # of a maximum matching in the bipartite graph that |
|---|
| 408 | # servers_of_happiness() makes between serverids and share |
|---|
| 409 | # numbers. It should find something like this: |
|---|
| 410 | # (server 1, share 1) |
|---|
| 411 | # (server 2, share 2) |
|---|
| 412 | # (server 3, share 3) |
|---|
| 413 | # (server 5, share 4) |
|---|
| 414 | # (server 6, share 5) |
|---|
| 415 | # |
|---|
| 416 | # and, since there are 5 edges in this matching, it should |
|---|
| 417 | # return 5. |
|---|
| 418 | test2 = merge_servers(test, set(trackers)) |
|---|
| 419 | happy = servers_of_happiness(test2) |
|---|
| 420 | self.failUnlessEqual(5, happy) |
|---|
| 421 | # Zooko's first puzzle: |
|---|
| 422 | # (from http://allmydata.org/trac/tahoe-lafs/ticket/778#comment:156) |
|---|
| 423 | # |
|---|
| 424 | # server 1: shares 0, 1 |
|---|
| 425 | # server 2: shares 1, 2 |
|---|
| 426 | # server 3: share 2 |
|---|
| 427 | # |
|---|
| 428 | # This should yield happiness of 3. |
|---|
| 429 | test = { |
|---|
| 430 | 0 : set(['server1']), |
|---|
| 431 | 1 : set(['server1', 'server2']), |
|---|
| 432 | 2 : set(['server2', 'server3']), |
|---|
| 433 | } |
|---|
| 434 | self.failUnlessEqual(3, servers_of_happiness(test)) |
|---|
| 435 | # Zooko's second puzzle: |
|---|
| 436 | # (from http://allmydata.org/trac/tahoe-lafs/ticket/778#comment:158) |
|---|
| 437 | # |
|---|
| 438 | # server 1: shares 0, 1 |
|---|
| 439 | # server 2: share 1 |
|---|
| 440 | # |
|---|
| 441 | # This should yield happiness of 2. |
|---|
| 442 | test = { |
|---|
| 443 | 0 : set(['server1']), |
|---|
| 444 | 1 : set(['server1', 'server2']), |
|---|
| 445 | } |
|---|
| 446 | self.failUnlessEqual(2, servers_of_happiness(test)) |
|---|
| 447 | |
|---|
| 448 | |
|---|
| 449 | def test_shares_by_server(self): |
|---|
| 450 | test = dict([(i, set(["server%d" % i])) for i in range(1, 5)]) |
|---|
| 451 | sbs = shares_by_server(test) |
|---|
| 452 | self.failUnlessEqual(set([1]), sbs["server1"]) |
|---|
| 453 | self.failUnlessEqual(set([2]), sbs["server2"]) |
|---|
| 454 | self.failUnlessEqual(set([3]), sbs["server3"]) |
|---|
| 455 | self.failUnlessEqual(set([4]), sbs["server4"]) |
|---|
| 456 | test1 = { |
|---|
| 457 | 1 : set(["server1"]), |
|---|
| 458 | 2 : set(["server1"]), |
|---|
| 459 | 3 : set(["server1"]), |
|---|
| 460 | 4 : set(["server2"]), |
|---|
| 461 | 5 : set(["server2"]) |
|---|
| 462 | } |
|---|
| 463 | sbs = shares_by_server(test1) |
|---|
| 464 | self.failUnlessEqual(set([1, 2, 3]), sbs["server1"]) |
|---|
| 465 | self.failUnlessEqual(set([4, 5]), sbs["server2"]) |
|---|
| 466 | # This should fail unless the serverid part of the mapping is a set |
|---|
| 467 | test2 = {1: "server1"} |
|---|
| 468 | self.shouldFail(AssertionError, |
|---|
| 469 | "test_shares_by_server", |
|---|
| 470 | "", |
|---|
| 471 | shares_by_server, test2) |
|---|