From 9b17b95487a7ec9d9a5423c9f4d6f2d2a5692712 Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 13:13:50 +0500 Subject: [PATCH 1/7] Introduce LocustsCollection class, responsible for accurate locusts spawning/killing --- locust/locusts_collection.py | 58 ++++++++++++++++++ locust/test/test_locusts_collection.py | 84 ++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100755 locust/locusts_collection.py create mode 100644 locust/test/test_locusts_collection.py diff --git a/locust/locusts_collection.py b/locust/locusts_collection.py new file mode 100755 index 0000000000..14d24f328c --- /dev/null +++ b/locust/locusts_collection.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +import math + +class _LocustInfo(object): + def __init__(self, locust, ratio): + self.locust = locust + self.ratio = ratio + self.count = 0 + + def lower_count(self, total_count): + return int(math.floor(self.ratio * total_count)) + + def upper_count(self, total_count): + return int(math.ceil(self.ratio * total_count)) + + def miscount(self, total_count): + return total_count * self.ratio - self.count + +class LocustsCollection(object): + def __init__(self, locust_classes): + total_weight = sum(locust.weight for locust in locust_classes) + self._locusts = [_LocustInfo(locust, locust.weight / float(total_weight)) + for locust in locust_classes] + self.size = 0 + + @property + def classes(self): + return list(map(lambda l: l.locust, self._locusts)) + + def spawn_locusts(self, spawn_count): + spawned = [] + new_size = self.size + spawn_count + for locust in self._locusts: + adjust_spawn_count = max(0, locust.lower_count(new_size) - locust.count) + spawned.extend(self._change_locust_count(locust, adjust_spawn_count)) + return spawned + self._make_final_size_adjusment(new_size) + + def kill_locusts(self, kill_count): + killed = [] + new_size = self.size - kill_count + for locust in self._locusts: + adjust_kill_count = max(0, locust.count - locust.upper_count(new_size)) + killed.extend(self._change_locust_count(locust, -adjust_kill_count)) + return killed + self._make_final_size_adjusment(new_size) + + def _change_locust_count(self, locust, count): + self.size += count + locust.count += count + return [locust.locust for _ in range(abs(count))] + + def _make_final_size_adjusment(self, new_size): + adjusted = [] + adjusted_size = abs(self.size - new_size) + add_locusts = self.size < new_size + sorted_locusts = sorted(self._locusts, key=lambda l: l.miscount(new_size), reverse=add_locusts) + for locust in list(sorted_locusts)[:adjusted_size]: + adjusted.extend(self._change_locust_count(locust, 1 if add_locusts else -1)) + return adjusted diff --git a/locust/test/test_locusts_collection.py b/locust/test/test_locusts_collection.py new file mode 100644 index 0000000000..5cc59ac272 --- /dev/null +++ b/locust/test/test_locusts_collection.py @@ -0,0 +1,84 @@ +import unittest + +from locust.locusts_collection import LocustsCollection + +class MockLocust(object): + def __init__(self, weight): + self.weight = weight + def __str__(self): + return 'locust(w={})'.format(self.weight) + def __repr__(self): + return 'MockLocust({})'.format(self.weight) +class TestLocustsCollection(unittest.TestCase): + def test_spawn_locusts_1_class(self): + locust = MockLocust(1) + collection = LocustsCollection([locust]) + spawned = collection.spawn_locusts(4) + self.assertItemsEqual(spawned, [locust, locust, locust, locust]) + + def test_spawn_locusts_3_classes(self): + l1, l2, l3 = MockLocust(1), MockLocust(1), MockLocust(1) + collection = LocustsCollection([l1, l2, l3]) + s1 = collection.spawn_locusts(1) + s2 = collection.spawn_locusts(1) + s3 = collection.spawn_locusts(1) + self.assertItemsEqual(s1 + s2 + s3, [l1, l2, l3]) + + def test_kill_locusts_1_class(self): + locust = MockLocust(1) + collection = LocustsCollection([locust]) + spawned = collection.spawn_locusts(4) + killed1 = collection.kill_locusts(3) + killed2 = collection.kill_locusts(1) + self.assertItemsEqual(killed1, [locust, locust, locust]) + self.assertItemsEqual(killed2, [locust]) + + def test_kill_locusts_3_classes(self): + l1, l2, l3 = MockLocust(1), MockLocust(1), MockLocust(1) + collection = LocustsCollection([l1, l2, l3]) + spawned = collection.spawn_locusts(3) + k1 = collection.kill_locusts(1) + k2 = collection.kill_locusts(1) + k3 = collection.kill_locusts(1) + self.assertItemsEqual(k1 + k2 + k3, [l1, l2, l3]) + + def test_spawn_complex_weight_distribution(self): + l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) + collection = LocustsCollection([l1, l3, l5]) + s1 = collection.spawn_locusts(1) + s2 = collection.spawn_locusts(3) + s3 = collection.spawn_locusts(5) + self.assertItemsEqual(s1, [l5]) + self.assertItemsEqual(s2, [l1, l3, l5]) + self.assertItemsEqual(s3, [l3, l3, l5, l5, l5]) + + def test_kill_complex_weight_distribution(self): + l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) + collection = LocustsCollection([l1, l3, l5]) + spawned = collection.spawn_locusts(9) + k1 = collection.kill_locusts(1) + k2 = collection.kill_locusts(3) + k3 = collection.kill_locusts(5) + self.assertItemsEqual(k1, [l5]) + self.assertItemsEqual(k2, [l1, l3, l5]) + self.assertItemsEqual(k3, [l3, l3, l5, l5, l5]) + + def test_spawn_and_kill(self): + l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) + collection = LocustsCollection([l1, l3, l5]) + s1 = collection.spawn_locusts(4) + k1 = collection.kill_locusts(1) + k2 = collection.kill_locusts(2) + s2 = collection.spawn_locusts(3) + self.assertItemsEqual(s1, [l1, l3, l5, l5]) + self.assertItemsEqual(k1, [l1]) + self.assertItemsEqual(k2, [l3, l5]) + self.assertItemsEqual(s2, [l1, l3, l5]) + + def test_spawn_many_locusts(self): + l1, l3, l5 = MockLocust(2), MockLocust(3), MockLocust(5) + collection = LocustsCollection([l1, l3, l5]) + spawned = collection.spawn_locusts(100) + self.assertItemsEqual(spawned, [l1 for _ in range(20)] + + [l3 for _ in range(30)] + + [l5 for _ in range(50)]) From e080848e9f5e6207410b90cd258289a4cb6cc6a0 Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 14:22:13 +0500 Subject: [PATCH 2/7] Refactor code & tests a little --- locust/runners.py | 15 +++++++---- locust/test/test_runners.py | 54 ++++++++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/locust/runners.py b/locust/runners.py index f397cd5646..c6dd0981ff 100644 --- a/locust/runners.py +++ b/locust/runners.py @@ -30,7 +30,7 @@ class LocustRunner(object): def __init__(self, locust_classes, options): self.options = options - self.locust_classes = locust_classes + self.locust_classes = self.filter_true_locust_classes(locust_classes) self.hatch_rate = options.hatch_rate self.num_clients = options.num_clients self.host = options.host @@ -61,6 +61,15 @@ def errors(self): def user_count(self): return len(self.locusts) + def filter_true_locust_classes(self, locust_classes): + true_locust_classes = [] + for locust in locust_classes: + if not locust.task_set: + warnings.warn("Notice: Found Locust class (%s.%s) got no task_set. Skipping..." % (locust.__module__, locust.__name__)) + else: + true_locust_classes.append(locust) + return true_locust_classes + def weight_locusts(self, amount, stop_timeout = None): """ Distributes the amount of locusts for each WebLocust-class according to it's weight @@ -69,10 +78,6 @@ def weight_locusts(self, amount, stop_timeout = None): bucket = [] weight_sum = sum((locust.weight for locust in self.locust_classes if locust.task_set)) for locust in self.locust_classes: - if not locust.task_set: - warnings.warn("Notice: Found Locust class (%s) got no task_set. Skipping..." % locust.__name__) - continue - if self.host is not None: locust.host = self.host if stop_timeout is not None: diff --git a/locust/test/test_runners.py b/locust/test/test_runners.py index 73d2c65dc6..225d74fc83 100644 --- a/locust/test/test_runners.py +++ b/locust/test/test_runners.py @@ -57,11 +57,16 @@ def tearDown(self): def test_slave_connect(self): import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) server.mocked_send(Message("client_ready", None, "zeh_fake_client1")) self.assertEqual(1, len(master.clients)) self.assertTrue("zeh_fake_client1" in master.clients, "Could not find fake client in master instance's clients dict") @@ -76,11 +81,16 @@ class MyTestLocust(Locust): def test_slave_stats_report_median(self): import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) server.mocked_send(Message("client_ready", None, "fake_client")) master.stats.get("/", "GET").log(100, 23455) @@ -98,11 +108,16 @@ class MyTestLocust(Locust): def test_master_total_stats(self): import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) server.mocked_send(Message("client_ready", None, "fake_client")) stats = RequestStats() stats.log_request("GET", "/1", 100, 3546) @@ -126,15 +141,20 @@ class MyTestLocust(Locust): def test_master_current_response_times(self): import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet start_time = 1 with mock.patch("time.time") as mocked_time: mocked_time.return_value = start_time global_stats.reset_all() with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) mocked_time.return_value += 1 server.mocked_send(Message("client_ready", None, "fake_client")) stats = RequestStats() @@ -205,11 +225,16 @@ def test_spawn_uneven_locusts(self): """ import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) for i in range(5): server.mocked_send(Message("client_ready", None, "fake_client%i" % i)) @@ -225,11 +250,16 @@ class MyTestLocust(Locust): def test_spawn_fewer_locusts_than_slaves(self): import mock + class MyTaskSet(TaskSet): + @task + def my_task(self): + pass + class MyTestLocust(Locust): - pass + task_set = MyTaskSet with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: - master = MasterLocustRunner(MyTestLocust, self.options) + master = MasterLocustRunner([MyTestLocust], self.options) for i in range(5): server.mocked_send(Message("client_ready", None, "fake_client%i" % i)) From ab81160d077358afa91c7865e35588931593dd0c Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 14:49:48 +0500 Subject: [PATCH 3/7] Inject LocustsCollection into base LocustRunner --- locust/runners.py | 36 +++++++++++++----------------------- locust/web.py | 2 +- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/locust/runners.py b/locust/runners.py index c6dd0981ff..325bab8520 100644 --- a/locust/runners.py +++ b/locust/runners.py @@ -5,6 +5,7 @@ import traceback import warnings from hashlib import md5 +from collections import Counter from time import time import gevent @@ -17,6 +18,7 @@ from . import events from .rpc import Message, rpc from .stats import global_stats +from .locusts_collection import LocustsCollection logger = logging.getLogger(__name__) @@ -30,7 +32,7 @@ class LocustRunner(object): def __init__(self, locust_classes, options): self.options = options - self.locust_classes = self.filter_true_locust_classes(locust_classes) + self.locusts_collection = LocustsCollection(self.filter_true_locust_classes(locust_classes)) self.hatch_rate = options.hatch_rate self.num_clients = options.num_clients self.host = options.host @@ -70,31 +72,20 @@ def filter_true_locust_classes(self, locust_classes): true_locust_classes.append(locust) return true_locust_classes - def weight_locusts(self, amount, stop_timeout = None): - """ - Distributes the amount of locusts for each WebLocust-class according to it's weight - returns a list "bucket" with the weighted locusts - """ - bucket = [] - weight_sum = sum((locust.weight for locust in self.locust_classes if locust.task_set)) - for locust in self.locust_classes: + def update_locusts_settings(self, locusts, stop_timeout=None): + for locust in self.locusts: if self.host is not None: locust.host = self.host if stop_timeout is not None: locust.stop_timeout = stop_timeout - - # create locusts depending on weight - percent = locust.weight / float(weight_sum) - num_locusts = int(round(amount * percent)) - bucket.extend([locust for x in xrange(0, num_locusts)]) - return bucket + return locusts def spawn_locusts(self, spawn_count=None, stop_timeout=None, wait=False): if spawn_count is None: spawn_count = self.num_clients - bucket = self.weight_locusts(spawn_count, stop_timeout) - spawn_count = len(bucket) + bucket = self.locusts_collection.spawn_locusts(spawn_count) + bucket = self.update_locusts_settings(bucket, stop_timeout) if self.state == STATE_INIT or self.state == STATE_STOPPED: self.state = STATE_HATCHING self.num_clients = spawn_count @@ -102,18 +93,17 @@ def spawn_locusts(self, spawn_count=None, stop_timeout=None, wait=False): self.num_clients += spawn_count logger.info("Hatching and swarming %i clients at the rate %g clients/s..." % (spawn_count, self.hatch_rate)) - occurence_count = dict([(l.__name__, 0) for l in self.locust_classes]) - + def hatch(): sleep_time = 1.0 / self.hatch_rate while True: if not bucket: - logger.info("All locusts hatched: %s" % ", ".join(["%s: %d" % (name, count) for name, count in six.iteritems(occurence_count)])) + hatched_locusts = Counter(map(lambda l: l.__name__, bucket)) + logger.info("All locusts hatched: %s" % ", ".join(["%s: %d" % (name, count) for name, count in hatched_locusts.items()])) events.hatch_complete.fire(user_count=self.num_clients) return locust = bucket.pop(random.randint(0, len(bucket)-1)) - occurence_count[locust.__name__] += 1 def start_locust(_): try: locust().run(runner=self) @@ -133,8 +123,8 @@ def kill_locusts(self, kill_count): """ Kill a kill_count of weighted locusts from the Group() object in self.locusts """ - bucket = self.weight_locusts(kill_count) - kill_count = len(bucket) + bucket = self.locusts_collection.kill_locusts(kill_count) + bucket = self.update_locusts_settings(bucket) self.num_clients -= kill_count logger.info("Killing %i locusts" % kill_count) dying = [] diff --git a/locust/web.py b/locust/web.py index aa31599ec4..6ad9c62d02 100644 --- a/locust/web.py +++ b/locust/web.py @@ -39,7 +39,7 @@ def index(): if runners.locust_runner.host: host = runners.locust_runner.host - elif len(runners.locust_runner.locust_classes) > 0: + elif runners.locust_runner.locusts_collection.classes: host = runners.locust_runner.locust_classes[0].host else: host = None From 23088aabaa1c630cf01e0064375b39f284cd1492 Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 15:02:28 +0500 Subject: [PATCH 4/7] Refactor a little bit awkward code for spawning locusts --- locust/runners.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/locust/runners.py b/locust/runners.py index 325bab8520..4dbf6bfe34 100644 --- a/locust/runners.py +++ b/locust/runners.py @@ -94,27 +94,23 @@ def spawn_locusts(self, spawn_count=None, stop_timeout=None, wait=False): logger.info("Hatching and swarming %i clients at the rate %g clients/s..." % (spawn_count, self.hatch_rate)) - def hatch(): - sleep_time = 1.0 / self.hatch_rate - while True: - if not bucket: - hatched_locusts = Counter(map(lambda l: l.__name__, bucket)) - logger.info("All locusts hatched: %s" % ", ".join(["%s: %d" % (name, count) for name, count in hatched_locusts.items()])) - events.hatch_complete.fire(user_count=self.num_clients) - return - - locust = bucket.pop(random.randint(0, len(bucket)-1)) - def start_locust(_): - try: - locust().run(runner=self) - except GreenletExit: - pass - new_locust = self.locusts.spawn(start_locust, locust) - if len(self.locusts) % 10 == 0: - logger.debug("%i locusts hatched" % len(self.locusts)) - gevent.sleep(sleep_time) - - hatch() + sleep_time = 1.0 / self.hatch_rate + random.shuffle(bucket) + for locust in bucket: + def start_locust(_): + try: + locust().run(runner=self) + except GreenletExit: + pass + new_locust = self.locusts.spawn(start_locust, locust) + if len(self.locusts) % 10 == 0: + logger.debug("%i locusts hatched" % len(self.locusts)) + gevent.sleep(sleep_time) + + hatched_locusts = Counter(map(lambda l: l.__name__, bucket)) + logger.info("All locusts hatched: %s" % ", ".join(["%s: %d" % (name, count) for name, count in hatched_locusts.items()])) + events.hatch_complete.fire(user_count=self.num_clients) + if wait: self.locusts.join() logger.info("All locusts dead\n") From 6a05c0bbba5a4599400ed3250fb4dfc0ffdcb58b Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 15:03:18 +0500 Subject: [PATCH 5/7] Add simple description of LocustsCollection class --- locust/locusts_collection.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locust/locusts_collection.py b/locust/locusts_collection.py index 14d24f328c..3fb602c12a 100755 --- a/locust/locusts_collection.py +++ b/locust/locusts_collection.py @@ -17,6 +17,14 @@ def miscount(self, total_count): return total_count * self.ratio - self.count class LocustsCollection(object): + """ + LocustCollection maintain accurate distribution of locust classes among available locust executors according to the locust weight + Algorithm maintain next invariant: + 1. Let p_i = weight_i / sum(weights) + 2. Let count_i = p_i * locusts_count + 3. Then, for every locust class there is at most two interger points near to count_i: floor(count_i) and ceil(count_i) + 4. At every moment each locust class executed by floor(count_i) or ceil(count_i) locust executors + """ def __init__(self, locust_classes): total_weight = sum(locust.weight for locust in locust_classes) self._locusts = [_LocustInfo(locust, locust.weight / float(total_weight)) From 21f401fbe5d9facbd737ebba8e4cebbe90f86979 Mon Sep 17 00:00:00 2001 From: Umqra Date: Sun, 5 Aug 2018 15:41:07 +0500 Subject: [PATCH 6/7] Replace assertItemsEqual method(available only in Python2.7) with custom assertion --- locust/test/test_locusts_collection.py | 39 ++++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/locust/test/test_locusts_collection.py b/locust/test/test_locusts_collection.py index 5cc59ac272..2425d2124f 100644 --- a/locust/test/test_locusts_collection.py +++ b/locust/test/test_locusts_collection.py @@ -1,6 +1,7 @@ import unittest from locust.locusts_collection import LocustsCollection +from collections import Counter class MockLocust(object): def __init__(self, weight): @@ -10,11 +11,14 @@ def __str__(self): def __repr__(self): return 'MockLocust({})'.format(self.weight) class TestLocustsCollection(unittest.TestCase): + def assertEqualUnorderedCollections(self, a, b): + self.assertEqual(Counter(a), Counter(b)) + def test_spawn_locusts_1_class(self): locust = MockLocust(1) collection = LocustsCollection([locust]) spawned = collection.spawn_locusts(4) - self.assertItemsEqual(spawned, [locust, locust, locust, locust]) + self.assertEqualUnorderedCollections(spawned, [locust, locust, locust, locust]) def test_spawn_locusts_3_classes(self): l1, l2, l3 = MockLocust(1), MockLocust(1), MockLocust(1) @@ -22,7 +26,7 @@ def test_spawn_locusts_3_classes(self): s1 = collection.spawn_locusts(1) s2 = collection.spawn_locusts(1) s3 = collection.spawn_locusts(1) - self.assertItemsEqual(s1 + s2 + s3, [l1, l2, l3]) + self.assertEqualUnorderedCollections(s1 + s2 + s3, [l1, l2, l3]) def test_kill_locusts_1_class(self): locust = MockLocust(1) @@ -30,8 +34,8 @@ def test_kill_locusts_1_class(self): spawned = collection.spawn_locusts(4) killed1 = collection.kill_locusts(3) killed2 = collection.kill_locusts(1) - self.assertItemsEqual(killed1, [locust, locust, locust]) - self.assertItemsEqual(killed2, [locust]) + self.assertEqualUnorderedCollections(killed1, [locust, locust, locust]) + self.assertEqualUnorderedCollections(killed2, [locust]) def test_kill_locusts_3_classes(self): l1, l2, l3 = MockLocust(1), MockLocust(1), MockLocust(1) @@ -40,7 +44,7 @@ def test_kill_locusts_3_classes(self): k1 = collection.kill_locusts(1) k2 = collection.kill_locusts(1) k3 = collection.kill_locusts(1) - self.assertItemsEqual(k1 + k2 + k3, [l1, l2, l3]) + self.assertEqualUnorderedCollections(k1 + k2 + k3, [l1, l2, l3]) def test_spawn_complex_weight_distribution(self): l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) @@ -48,9 +52,9 @@ def test_spawn_complex_weight_distribution(self): s1 = collection.spawn_locusts(1) s2 = collection.spawn_locusts(3) s3 = collection.spawn_locusts(5) - self.assertItemsEqual(s1, [l5]) - self.assertItemsEqual(s2, [l1, l3, l5]) - self.assertItemsEqual(s3, [l3, l3, l5, l5, l5]) + self.assertEqualUnorderedCollections(s1, [l5]) + self.assertEqualUnorderedCollections(s2, [l1, l3, l5]) + self.assertEqualUnorderedCollections(s3, [l3, l3, l5, l5, l5]) def test_kill_complex_weight_distribution(self): l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) @@ -59,9 +63,9 @@ def test_kill_complex_weight_distribution(self): k1 = collection.kill_locusts(1) k2 = collection.kill_locusts(3) k3 = collection.kill_locusts(5) - self.assertItemsEqual(k1, [l5]) - self.assertItemsEqual(k2, [l1, l3, l5]) - self.assertItemsEqual(k3, [l3, l3, l5, l5, l5]) + self.assertEqualUnorderedCollections(k1, [l5]) + self.assertEqualUnorderedCollections(k2, [l1, l3, l5]) + self.assertEqualUnorderedCollections(k3, [l3, l3, l5, l5, l5]) def test_spawn_and_kill(self): l1, l3, l5 = MockLocust(1), MockLocust(3), MockLocust(5) @@ -70,15 +74,14 @@ def test_spawn_and_kill(self): k1 = collection.kill_locusts(1) k2 = collection.kill_locusts(2) s2 = collection.spawn_locusts(3) - self.assertItemsEqual(s1, [l1, l3, l5, l5]) - self.assertItemsEqual(k1, [l1]) - self.assertItemsEqual(k2, [l3, l5]) - self.assertItemsEqual(s2, [l1, l3, l5]) + self.assertEqualUnorderedCollections(s1, [l1, l3, l5, l5]) + self.assertEqualUnorderedCollections(k1, [l1]) + self.assertEqualUnorderedCollections(k2, [l3, l5]) + self.assertEqualUnorderedCollections(s2, [l1, l3, l5]) def test_spawn_many_locusts(self): l1, l3, l5 = MockLocust(2), MockLocust(3), MockLocust(5) collection = LocustsCollection([l1, l3, l5]) spawned = collection.spawn_locusts(100) - self.assertItemsEqual(spawned, [l1 for _ in range(20)] + - [l3 for _ in range(30)] + - [l5 for _ in range(50)]) + expected = [l1 for _ in range(20)] + [l3 for _ in range(30)] + [l5 for _ in range(50)] + self.assertEqualUnorderedCollections(spawned, expected) From 5098b1ed483a639fd5d9e31b5bd5a2a4a53fd182 Mon Sep 17 00:00:00 2001 From: Umqra Date: Mon, 8 Oct 2018 23:18:23 +0500 Subject: [PATCH 7/7] Fix bug in locust runner --- locust/runners.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locust/runners.py b/locust/runners.py index 4dbf6bfe34..9c03f7e556 100644 --- a/locust/runners.py +++ b/locust/runners.py @@ -73,7 +73,7 @@ def filter_true_locust_classes(self, locust_classes): return true_locust_classes def update_locusts_settings(self, locusts, stop_timeout=None): - for locust in self.locusts: + for locust in locusts: if self.host is not None: locust.host = self.host if stop_timeout is not None: