1#!/usr/bin/python 2#pylint: disable-msg=C0111 3 4# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 5# Use of this source code is governed by a BSD-style license that can be 6# found in the LICENSE file. 7import collections 8import unittest 9 10import common 11from autotest_lib.client.common_lib import host_queue_entry_states 12from autotest_lib.frontend import setup_django_environment 13from autotest_lib.frontend.afe import frontend_test_utils 14from autotest_lib.frontend.afe import models 15from autotest_lib.frontend.afe import rdb_model_extensions 16from autotest_lib.scheduler import rdb 17from autotest_lib.scheduler import rdb_hosts 18from autotest_lib.scheduler import rdb_lib 19from autotest_lib.scheduler import rdb_requests 20from autotest_lib.scheduler import rdb_testing_utils 21from autotest_lib.server.cros import provision 22 23 24class AssignmentValidator(object): 25 """Utility class to check that priority inversion doesn't happen. """ 26 27 28 @staticmethod 29 def check_acls_deps(host, request): 30 """Check if a host and request match by comparing acls and deps. 31 32 @param host: A dictionary representing attributes of the host. 33 @param request: A request, as defined in rdb_requests. 34 35 @return True if the deps/acls of the request match the host. 36 """ 37 # Unfortunately the hosts labels are labelnames, not ids. 38 request_deps = set([l.name for l in 39 models.Label.objects.filter(id__in=request.deps)]) 40 return (set(host['labels']).intersection(request_deps) == request_deps 41 and set(host['acls']).intersection(request.acls)) 42 43 44 @staticmethod 45 def find_matching_host_for_request(hosts, request): 46 """Find a host from the given list of hosts, matching the request. 47 48 @param hosts: A list of dictionaries representing host attributes. 49 @param requetst: The unsatisfied request. 50 51 @return: A host, if a matching host is found from the input list. 52 """ 53 if not hosts or not request: 54 return None 55 for host in hosts: 56 if AssignmentValidator.check_acls_deps(host, request): 57 return host 58 59 60 @staticmethod 61 def sort_requests(requests): 62 """Sort the requests by priority. 63 64 @param requests: Unordered requests. 65 66 @return: A list of requests ordered by priority. 67 """ 68 return sorted(collections.Counter(requests).items(), 69 key=lambda request: request[0].priority, reverse=True) 70 71 72 @staticmethod 73 def verify_priority(request_queue, result): 74 requests = AssignmentValidator.sort_requests(request_queue) 75 for request, count in requests: 76 hosts = result.get(request) 77 # The request was completely satisfied. 78 if hosts and len(hosts) == count: 79 continue 80 # Go through all hosts given to lower priority requests and 81 # make sure we couldn't have allocated one of them for this 82 # unsatisfied higher priority request. 83 lower_requests = requests[requests.index((request,count))+1:] 84 for lower_request, count in lower_requests: 85 if (lower_request.priority < request.priority and 86 AssignmentValidator.find_matching_host_for_request( 87 result.get(lower_request), request)): 88 raise ValueError('Priority inversion occured between ' 89 'priorities %s and %s' % 90 (request.priority, lower_request.priority)) 91 92 93 @staticmethod 94 def priority_checking_response_handler(request_manager): 95 """Fake response handler wrapper for any request_manager. 96 97 Check that higher priority requests get a response over lower priority 98 requests, by re-validating all the hosts assigned to a lower priority 99 request against the unsatisfied higher priority ones. 100 101 @param request_manager: A request_manager as defined in rdb_lib. 102 103 @raises ValueError: If priority inversion is detected. 104 """ 105 # Fist call the rdb to make its decisions, then sort the requests 106 # by priority and make sure unsatisfied requests higher up in the list 107 # could not have been satisfied by hosts assigned to requests lower 108 # down in the list. 109 result = request_manager.api_call(request_manager.request_queue) 110 if not result: 111 raise ValueError('Expected results but got none.') 112 AssignmentValidator.verify_priority( 113 request_manager.request_queue, result) 114 for hosts in result.values(): 115 for host in hosts: 116 yield host 117 118 119class BaseRDBTest(rdb_testing_utils.AbstractBaseRDBTester, unittest.TestCase): 120 _config_section = 'AUTOTEST_WEB' 121 122 123 def testAcquireLeasedHostBasic(self): 124 """Test that acquisition of a leased host doesn't happen. 125 126 @raises AssertionError: If the one host that satisfies the request 127 is acquired. 128 """ 129 job = self.create_job(deps=set(['a'])) 130 host = self.db_helper.create_host('h1', deps=set(['a'])) 131 host.leased = 1 132 host.save() 133 queue_entries = self._dispatcher._refresh_pending_queue_entries() 134 hosts = list(rdb_lib.acquire_hosts(queue_entries)) 135 self.assertTrue(len(hosts) == 1 and hosts[0] is None) 136 137 138 def testAcquireLeasedHostRace(self): 139 """Test behaviour when hosts are leased just before acquisition. 140 141 If a fraction of the hosts somehow get leased between finding and 142 acquisition, the rdb should just return the remaining hosts for the 143 request to use. 144 145 @raises AssertionError: If both the requests get a host successfully, 146 since one host gets leased before the final attempt to lease both. 147 """ 148 j1 = self.create_job(deps=set(['a'])) 149 j2 = self.create_job(deps=set(['a'])) 150 hosts = [self.db_helper.create_host('h1', deps=set(['a'])), 151 self.db_helper.create_host('h2', deps=set(['a']))] 152 153 @rdb_hosts.return_rdb_host 154 def local_find_hosts(host_query_manger, deps, acls): 155 """Return a predetermined list of hosts, one of which is leased.""" 156 h1 = models.Host.objects.get(hostname='h1') 157 h1.leased = 1 158 h1.save() 159 h2 = models.Host.objects.get(hostname='h2') 160 return [h1, h2] 161 162 self.god.stub_with(rdb.AvailableHostQueryManager, 'find_hosts', 163 local_find_hosts) 164 queue_entries = self._dispatcher._refresh_pending_queue_entries() 165 hosts = list(rdb_lib.acquire_hosts(queue_entries)) 166 self.assertTrue(len(hosts) == 2 and None in hosts) 167 self.check_hosts(iter(hosts)) 168 169 170 def testHostReleaseStates(self): 171 """Test that we will only release an unused host if it is in Ready. 172 173 @raises AssertionError: If the host gets released in any other state. 174 """ 175 host = self.db_helper.create_host('h1', deps=set(['x'])) 176 for state in rdb_model_extensions.AbstractHostModel.Status.names: 177 host.status = state 178 host.leased = 1 179 host.save() 180 self._release_unused_hosts() 181 host = models.Host.objects.get(hostname='h1') 182 self.assertTrue(host.leased == (state != 'Ready')) 183 184 185 def testHostReleseHQE(self): 186 """Test that we will not release a ready host if it's being used. 187 188 @raises AssertionError: If the host is released even though it has 189 been assigned to an active hqe. 190 """ 191 # Create a host and lease it out in Ready. 192 host = self.db_helper.create_host('h1', deps=set(['x'])) 193 host.status = 'Ready' 194 host.leased = 1 195 host.save() 196 197 # Create a job and give its hqe the leased host. 198 job = self.create_job(deps=set(['x'])) 199 self.db_helper.add_host_to_job(host, job.id) 200 hqe = models.HostQueueEntry.objects.get(job_id=job.id) 201 202 # Activate the hqe by setting its state. 203 hqe.status = host_queue_entry_states.ACTIVE_STATUSES[0] 204 hqe.save() 205 206 # Make sure the hqes host isn't released, even if its in ready. 207 self._release_unused_hosts() 208 host = models.Host.objects.get(hostname='h1') 209 self.assertTrue(host.leased == 1) 210 211 212 def testBasicDepsAcls(self): 213 """Test a basic deps/acls request. 214 215 Make sure that a basic request with deps and acls, finds a host from 216 the ready pool that has matching labels and is in a matching aclgroups. 217 218 @raises AssertionError: If the request doesn't find a host, since the 219 we insert a matching host in the ready pool. 220 """ 221 deps = set(['a', 'b']) 222 acls = set(['a', 'b']) 223 self.db_helper.create_host('h1', deps=deps, acls=acls) 224 job = self.create_job(user='autotest_system', deps=deps, acls=acls) 225 queue_entries = self._dispatcher._refresh_pending_queue_entries() 226 matching_host = rdb_lib.acquire_hosts(queue_entries).next() 227 self.check_host_assignment(job.id, matching_host.id) 228 self.assertTrue(matching_host.leased == 1) 229 230 231 def testPreferredDeps(self): 232 """Test that perferred deps is respected. 233 234 If multiple hosts satisfied a job's deps, the one with preferred 235 label will be assigned to the job. 236 237 @raises AssertionError: If a host without a preferred label is 238 assigned to the job instead of one with 239 a preferred label. 240 """ 241 lumpy_deps = set(['board:lumpy']) 242 stumpy_deps = set(['board:stumpy']) 243 stumpy_deps_with_crosversion = set( 244 ['board:stumpy', 'cros-version:lumpy-release/R41-6323.0.0']) 245 246 acls = set(['a', 'b']) 247 # Hosts lumpy1 and lumpy2 are created as a control group, 248 # which ensures that if no preferred label is used, the host 249 # with a smaller id will be chosen first. We need to make sure 250 # stumpy2 was chosen because it has a cros-version label, but not 251 # because of other randomness. 252 self.db_helper.create_host('lumpy1', deps=lumpy_deps, acls=acls) 253 self.db_helper.create_host('lumpy2', deps=lumpy_deps, acls=acls) 254 self.db_helper.create_host('stumpy1', deps=stumpy_deps, acls=acls) 255 self.db_helper.create_host( 256 'stumpy2', deps=stumpy_deps_with_crosversion , acls=acls) 257 job_1 = self.create_job(user='autotest_system', 258 deps=lumpy_deps, acls=acls) 259 job_2 = self.create_job(user='autotest_system', 260 deps=stumpy_deps_with_crosversion, acls=acls) 261 queue_entries = self._dispatcher._refresh_pending_queue_entries() 262 matching_hosts = list(rdb_lib.acquire_hosts(queue_entries)) 263 assignment = {} 264 import logging 265 for job, host in zip(queue_entries, matching_hosts): 266 self.check_host_assignment(job.id, host.id) 267 assignment[job.id] = host.hostname 268 self.assertEqual(assignment[job_1.id], 'lumpy1') 269 self.assertEqual(assignment[job_2.id], 'stumpy2') 270 271 272 def testBadDeps(self): 273 """Test that we find no hosts when only acls match. 274 275 @raises AssertionError: If the request finds a host, since the only 276 host in the ready pool will not have matching deps. 277 """ 278 host_labels = set(['a']) 279 job_deps = set(['b']) 280 acls = set(['a', 'b']) 281 self.db_helper.create_host('h1', deps=host_labels, acls=acls) 282 job = self.create_job(user='autotest_system', deps=job_deps, acls=acls) 283 queue_entries = self._dispatcher._refresh_pending_queue_entries() 284 matching_host = rdb_lib.acquire_hosts(queue_entries).next() 285 self.assert_(not matching_host) 286 287 288 def testBadAcls(self): 289 """Test that we find no hosts when only deps match. 290 291 @raises AssertionError: If the request finds a host, since the only 292 host in the ready pool will not have matching acls. 293 """ 294 deps = set(['a']) 295 host_acls = set(['a']) 296 job_acls = set(['b']) 297 self.db_helper.create_host('h1', deps=deps, acls=host_acls) 298 299 # Create the job as a new user who is only in the 'b' and 'Everyone' 300 # aclgroups. Though there are several hosts in the Everyone group, the 301 # 1 host that has the 'a' dep isn't. 302 job = self.create_job(user='new_user', deps=deps, acls=job_acls) 303 queue_entries = self._dispatcher._refresh_pending_queue_entries() 304 matching_host = rdb_lib.acquire_hosts(queue_entries).next() 305 self.assert_(not matching_host) 306 307 308 def testBasicPriority(self): 309 """Test that priority inversion doesn't happen. 310 311 Schedule 2 jobs with the same deps, acls and user, but different 312 priorities, and confirm that the higher priority request gets the host. 313 This confirmation happens through the AssignmentValidator. 314 315 @raises AssertionError: If the un important request gets host h1 instead 316 of the important request. 317 """ 318 deps = set(['a', 'b']) 319 acls = set(['a', 'b']) 320 self.db_helper.create_host('h1', deps=deps, acls=acls) 321 important_job = self.create_job(user='autotest_system', 322 deps=deps, acls=acls, priority=2) 323 un_important_job = self.create_job(user='autotest_system', 324 deps=deps, acls=acls, priority=0) 325 queue_entries = self._dispatcher._refresh_pending_queue_entries() 326 327 self.god.stub_with(rdb_requests.BaseHostRequestManager, 'response', 328 AssignmentValidator.priority_checking_response_handler) 329 self.check_hosts(rdb_lib.acquire_hosts(queue_entries)) 330 331 332 def testPriorityLevels(self): 333 """Test that priority inversion doesn't happen. 334 335 Increases a job's priority and makes several requests for hosts, 336 checking that priority inversion doesn't happen. 337 338 @raises AssertionError: If the unimportant job gets h1 while it is 339 still unimportant, or doesn't get h1 while after it becomes the 340 most important job. 341 """ 342 deps = set(['a', 'b']) 343 acls = set(['a', 'b']) 344 self.db_helper.create_host('h1', deps=deps, acls=acls) 345 346 # Create jobs that will bucket differently and confirm that jobs in an 347 # earlier bucket get a host. 348 first_job = self.create_job(user='autotest_system', deps=deps, acls=acls) 349 important_job = self.create_job(user='autotest_system', deps=deps, 350 acls=acls, priority=2) 351 deps.pop() 352 unimportant_job = self.create_job(user='someother_system', deps=deps, 353 acls=acls, priority=1) 354 queue_entries = self._dispatcher._refresh_pending_queue_entries() 355 356 self.god.stub_with(rdb_requests.BaseHostRequestManager, 'response', 357 AssignmentValidator.priority_checking_response_handler) 358 self.check_hosts(rdb_lib.acquire_hosts(queue_entries)) 359 360 # Elevate the priority of the unimportant job, so we now have 361 # 2 jobs at the same priority. 362 self.db_helper.increment_priority(job_id=unimportant_job.id) 363 queue_entries = self._dispatcher._refresh_pending_queue_entries() 364 self._release_unused_hosts() 365 self.check_hosts(rdb_lib.acquire_hosts(queue_entries)) 366 367 # Prioritize the first job, and confirm that it gets the host over the 368 # jobs that got it the last time. 369 self.db_helper.increment_priority(job_id=unimportant_job.id) 370 queue_entries = self._dispatcher._refresh_pending_queue_entries() 371 self._release_unused_hosts() 372 self.check_hosts(rdb_lib.acquire_hosts(queue_entries)) 373 374 375 def testFrontendJobScheduling(self): 376 """Test that basic frontend job scheduling. 377 378 @raises AssertionError: If the received and requested host don't match, 379 or the mis-matching host is returned instead. 380 """ 381 deps = set(['x', 'y']) 382 acls = set(['a', 'b']) 383 384 # Create 2 frontend jobs and only one matching host. 385 matching_job = self.create_job(acls=acls, deps=deps) 386 matching_host = self.db_helper.create_host('h1', acls=acls, deps=deps) 387 mis_matching_job = self.create_job(acls=acls, deps=deps) 388 mis_matching_host = self.db_helper.create_host( 389 'h2', acls=acls, deps=deps.pop()) 390 self.db_helper.add_host_to_job(matching_host, matching_job.id) 391 self.db_helper.add_host_to_job(mis_matching_host, mis_matching_job.id) 392 393 # Check that only the matching host is returned, and that we get 'None' 394 # for the second request. 395 queue_entries = self._dispatcher._refresh_pending_queue_entries() 396 hosts = list(rdb_lib.acquire_hosts(queue_entries)) 397 self.assertTrue(len(hosts) == 2 and None in hosts) 398 returned_host = [host for host in hosts if host].pop() 399 self.assertTrue(matching_host.id == returned_host.id) 400 401 402 def testFrontendJobPriority(self): 403 """Test that frontend job scheduling doesn't ignore priorities. 404 405 @raises ValueError: If the priorities of frontend jobs are ignored. 406 """ 407 board = 'x' 408 high_priority = self.create_job(priority=2, deps=set([board])) 409 low_priority = self.create_job(priority=1, deps=set([board])) 410 host = self.db_helper.create_host('h1', deps=set([board])) 411 self.db_helper.add_host_to_job(host, low_priority.id) 412 self.db_helper.add_host_to_job(host, high_priority.id) 413 414 queue_entries = self._dispatcher._refresh_pending_queue_entries() 415 416 def local_response_handler(request_manager): 417 """Confirms that a higher priority frontend job gets a host. 418 419 @raises ValueError: If priority inversion happens and the job 420 with priority 1 gets the host instead. 421 """ 422 result = request_manager.api_call(request_manager.request_queue) 423 if not result: 424 raise ValueError('Excepted the high priority request to ' 425 'get a host, but the result is empty.') 426 for request, hosts in result.iteritems(): 427 if request.priority == 1: 428 raise ValueError('Priority of frontend job ignored.') 429 if len(hosts) > 1: 430 raise ValueError('Multiple hosts returned against one ' 431 'frontend job scheduling request.') 432 yield hosts[0] 433 434 self.god.stub_with(rdb_requests.BaseHostRequestManager, 'response', 435 local_response_handler) 436 self.check_hosts(rdb_lib.acquire_hosts(queue_entries)) 437 438 439 def testSuiteOrderedHostAcquisition(self): 440 """Test that older suite jobs acquire hosts first. 441 442 Make sure older suite jobs get hosts first, but not at the expense of 443 higher priority jobs. 444 445 @raises ValueError: If unexpected acquisitions occur, eg: 446 suite_job_2 acquires the last 2 hosts instead of suite_job_1. 447 isolated_important_job doesn't get any hosts. 448 Any job acquires more hosts than necessary. 449 """ 450 board = 'x' 451 452 # Create 2 suites such that the later suite has an ordering of deps 453 # that places it ahead of the earlier suite, if parent_job_id is 454 # ignored. 455 suite_without_dep = self.create_suite(num=2, priority=0, board=board) 456 457 suite_with_dep = self.create_suite(num=1, priority=0, board=board) 458 self.db_helper.add_deps_to_job(suite_with_dep[0], dep_names=list('y')) 459 460 # Create an important job that should be ahead of the first suite, 461 # because priority trumps parent_job_id and time of creation. 462 isolated_important_job = self.create_job(priority=3, deps=set([board])) 463 464 # Create 3 hosts, all with the deps to satisfy the last suite. 465 for i in range(0, 3): 466 self.db_helper.create_host('h%s' % i, deps=set([board, 'y'])) 467 468 queue_entries = self._dispatcher._refresh_pending_queue_entries() 469 470 def local_response_handler(request_manager): 471 """Reorder requests and check host acquisition. 472 473 @raises ValueError: If unexpected/no acquisitions occur. 474 """ 475 if any([request for request in request_manager.request_queue 476 if request.parent_job_id is None]): 477 raise ValueError('Parent_job_id can never be None.') 478 479 # This will result in the ordering: 480 # [suite_2_1, suite_1_*, suite_1_*, isolated_important_job] 481 # The priority scheduling order should be: 482 # [isolated_important_job, suite_1_*, suite_1_*, suite_2_1] 483 # Since: 484 # a. the isolated_important_job is the most important. 485 # b. suite_1 was created before suite_2, regardless of deps 486 disorderly_queue = sorted(request_manager.request_queue, 487 key=lambda r: -r.parent_job_id) 488 request_manager.request_queue = disorderly_queue 489 result = request_manager.api_call(request_manager.request_queue) 490 if not result: 491 raise ValueError('Expected results but got none.') 492 493 # Verify that the isolated_important_job got a host, and that the 494 # first suite got both remaining free hosts. 495 for request, hosts in result.iteritems(): 496 if request.parent_job_id == 0: 497 if len(hosts) > 1: 498 raise ValueError('First job acquired more hosts than ' 499 'necessary. Response map: %s' % result) 500 continue 501 if request.parent_job_id == 1: 502 if len(hosts) < 2: 503 raise ValueError('First suite job requests were not ' 504 'satisfied. Response_map: %s' % result) 505 continue 506 # The second suite job got hosts instead of one of 507 # the others. Eitherway this is a failure. 508 raise ValueError('Unexpected host acquisition ' 509 'Response map: %s' % result) 510 yield None 511 512 self.god.stub_with(rdb_requests.BaseHostRequestManager, 'response', 513 local_response_handler) 514 list(rdb_lib.acquire_hosts(queue_entries)) 515 516 517 def testConfigurations(self): 518 """Test that configurations don't matter. 519 @raises AssertionError: If the request doesn't find a host, 520 this will happen if configurations are not stripped out. 521 """ 522 self.god.stub_with(provision.Cleanup, 523 '_actions', 524 {'action': 'fakeTest'}) 525 job_labels = set(['action', 'a']) 526 host_deps = set(['a']) 527 db_host = self.db_helper.create_host('h1', deps=host_deps) 528 self.create_job(user='autotest_system', deps=job_labels) 529 queue_entries = self._dispatcher._refresh_pending_queue_entries() 530 matching_host = rdb_lib.acquire_hosts(queue_entries).next() 531 self.assert_(matching_host.id == db_host.id) 532 533 534class RDBMinDutTest( 535 rdb_testing_utils.AbstractBaseRDBTester, unittest.TestCase): 536 """Test AvailableHostRequestHandler""" 537 538 _config_section = 'AUTOTEST_WEB' 539 540 541 def min_dut_test_helper(self, num_hosts, suite_settings): 542 """A helper function to test min_dut logic. 543 544 @param num_hosts: Total number of hosts to create. 545 @param suite_settings: A dictionary specify how suites would be created 546 and verified. 547 E.g. {'priority': 10, 'num_jobs': 3, 548 'min_duts':2, 'expected_aquired': 1} 549 With this setting, will create a suite that has 3 550 child jobs, with priority 10 and min_duts 2. 551 The suite is expected to get 1 dut. 552 """ 553 acls = set(['fake_acl']) 554 hosts = [] 555 for i in range (0, num_hosts): 556 hosts.append(self.db_helper.create_host( 557 'h%d' % i, deps=set(['board:lumpy']), acls=acls)) 558 suites = {} 559 suite_min_duts = {} 560 for setting in suite_settings: 561 s = self.create_suite(num=setting['num_jobs'], 562 priority=setting['priority'], 563 board='board:lumpy', acls=acls) 564 # Empty list will be used to store acquired hosts. 565 suites[s['parent_job'].id] = (setting, []) 566 suite_min_duts[s['parent_job'].id] = setting['min_duts'] 567 queue_entries = self._dispatcher._refresh_pending_queue_entries() 568 matching_hosts = rdb_lib.acquire_hosts(queue_entries, suite_min_duts) 569 for host, queue_entry in zip(matching_hosts, queue_entries): 570 if host: 571 suites[queue_entry.job.parent_job_id][1].append(host) 572 573 for setting, hosts in suites.itervalues(): 574 self.assertEqual(len(hosts),setting['expected_aquired']) 575 576 577 def testHighPriorityTakeAll(self): 578 """Min duts not satisfied.""" 579 num_hosts = 1 580 suite1 = {'priority':20, 'num_jobs': 3, 'min_duts': 2, 581 'expected_aquired': 1} 582 suite2 = {'priority':10, 'num_jobs': 7, 'min_duts': 5, 583 'expected_aquired': 0} 584 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 585 586 587 def testHighPriorityMinSatisfied(self): 588 """High priority min duts satisfied.""" 589 num_hosts = 4 590 suite1 = {'priority':20, 'num_jobs': 4, 'min_duts': 2, 591 'expected_aquired': 2} 592 suite2 = {'priority':10, 'num_jobs': 7, 'min_duts': 5, 593 'expected_aquired': 2} 594 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 595 596 597 def testAllPrioritiesMinSatisfied(self): 598 """Min duts satisfied.""" 599 num_hosts = 7 600 suite1 = {'priority':20, 'num_jobs': 4, 'min_duts': 2, 601 'expected_aquired': 2} 602 suite2 = {'priority':10, 'num_jobs': 7, 'min_duts': 5, 603 'expected_aquired': 5} 604 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 605 606 607 def testHighPrioritySatisfied(self): 608 """Min duts satisfied, high priority suite satisfied.""" 609 num_hosts = 10 610 suite1 = {'priority':20, 'num_jobs': 4, 'min_duts': 2, 611 'expected_aquired': 4} 612 suite2 = {'priority':10, 'num_jobs': 7, 'min_duts': 5, 613 'expected_aquired': 6} 614 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 615 616 617 def testEqualPriorityFirstSuiteMinSatisfied(self): 618 """Equal priority, earlier suite got min duts.""" 619 num_hosts = 4 620 suite1 = {'priority':20, 'num_jobs': 4, 'min_duts': 2, 621 'expected_aquired': 2} 622 suite2 = {'priority':20, 'num_jobs': 7, 'min_duts': 5, 623 'expected_aquired': 2} 624 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 625 626 627 def testEqualPriorityAllSuitesMinSatisfied(self): 628 """Equal priority, all suites got min duts.""" 629 num_hosts = 7 630 suite1 = {'priority':20, 'num_jobs': 4, 'min_duts': 2, 631 'expected_aquired': 2} 632 suite2 = {'priority':20, 'num_jobs': 7, 'min_duts': 5, 633 'expected_aquired': 5} 634 self.min_dut_test_helper(num_hosts, [suite1, suite2]) 635 636 637if __name__ == '__main__': 638 unittest.main() 639