Beispiel #1
0
    def test_delete(self):
        test_wait_time = 0.5
        job_sleep_time = 10
        job_max_repeats = 30

        job1 = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
        job1.wait_sleep_time = job_sleep_time

        job2 = Job(rand_int(), 'b', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
        job2.wait_sleep_time = job_sleep_time

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))

        scheduler.create(job1)
        scheduler.create(job2)
        scheduler.run()

        scheduler.unschedule(job1)

        self.assertIn(job2, scheduler.jobs)
        self.assertNotIn(job1, scheduler.jobs)
        self.assertFalse(job1.keep_running)

        # run - 1
        # create - 2
        # delete - 1
        # on_max_repeats_reached - 0 (because of how long it takes to run job_max_repeats with test_wait_time)
        # 1+2+1 = 4
        self.assertEquals(scheduler.lock.called, 4)
Beispiel #2
0
    def test_job_greenlets(self):

        data = {'spawned':[], 'stopped': []}

        class FakeGreenlet(object):
            def __init__(self, run):
                self.run = self._run = run

            def kill(self, *args, **kwargs):
                data['stopped'].append([self, args, kwargs])

        def spawn(job):
            g = FakeGreenlet(job)
            data['spawned'].append(g)
            return g

        with patch('gevent.spawn', spawn):

            test_wait_time = 0.5
            job_sleep_time = 10
            job_max_repeats = 30

            job1 = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
            job1.wait_sleep_time = job_sleep_time

            job2 = Job(rand_int(), 'b', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
            job2.wait_sleep_time = job_sleep_time

            scheduler = Scheduler(dummy_callback)
            scheduler.lock = RLock()
            scheduler.iter_cb = iter_cb
            scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))

            scheduler.create(job1, spawn=False)
            scheduler.create(job2, spawn=False)
            scheduler.run()

            self.assertEquals(scheduler.job_greenlets[job1.name]._run, job1.run)
            self.assertEquals(scheduler.job_greenlets[job2.name]._run, job2.run)

            self.assertTrue(job1.keep_running)
            self.assertTrue(job2.keep_running)

            scheduler.unschedule(job1)

            self.assertFalse(job1.keep_running)
            self.assertTrue(job2.keep_running)

            self.assertNotIn(job1.name, scheduler.job_greenlets)
            self.assertEquals(scheduler.job_greenlets[job2.name]._run, job2.run)

            self.assertEquals(len(data['stopped']), 1)
            g, args, kwargs = data['stopped'][0]
            self.assertIs(g.run.im_func, job1.run.im_func) # That's how we know it was job1 deleted not job2
            self.assertIs(args, ())
            self.assertDictEqual(kwargs, {'timeout':2.0})
Beispiel #3
0
    def test_on_job_executed_cb(self):

        data = {'runs':[], 'ctx':[]}

        def get_context():
            ctx = {'name': rand_string(), 'type':SCHEDULER.JOB_TYPE.INTERVAL_BASED}
            data['ctx'].append(ctx)
            return ctx

        def on_job_executed_cb(ctx):
            data['runs'].append(ctx)

        test_wait_time = 0.5
        job_sleep_time = 0.1
        job_max_repeats = 10

        job = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
        job.wait_sleep_time = job_sleep_time
        job.get_context = get_context

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))
        scheduler.on_job_executed_cb = on_job_executed_cb

        scheduler.create(job, spawn=False)
        scheduler.run()

        self.assertEquals(len(data['runs']), len(data['ctx']))

        for idx, item in enumerate(data['runs']):
            self.assertEquals(data['ctx'][idx], item)
Beispiel #4
0
    def test_on_max_repeats_reached(self):

        test_wait_time = 0.5
        job_sleep_time = 0.02
        job_max_repeats = 3

        data = {'job': None, 'called': 0}

        job = Job(rand_int(),
                  'a',
                  SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                  Interval(seconds=0.1),
                  max_repeats=job_max_repeats)
        job.wait_sleep_time = job_sleep_time

        # Just to make sure it's inactive by default.
        self.assertTrue(job.is_active)

        scheduler = Scheduler(dummy_callback)
        data['old_on_max_repeats_reached'] = scheduler.on_max_repeats_reached

        def on_max_repeats_reached(job):
            data['job'] = job
            data['called'] += 1
            data['old_on_max_repeats_reached'](job)

        scheduler.on_max_repeats_reached = on_max_repeats_reached
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                  timedelta(seconds=test_wait_time))

        scheduler.create(job)
        scheduler.run()

        now = datetime.utcnow()

        # Now the job should have reached the max_repeats limit within the now - test_wait_time period.
        self.assertIs(job, data['job'])
        self.assertEquals(1, data['called'])
        self.assertTrue(job.max_repeats_reached)
        self.assertFalse(job.keep_running)
        self.assertTrue(job.max_repeats_reached_at < now)
        self.assertTrue(job.max_repeats_reached_at >= now +
                        timedelta(seconds=-test_wait_time))

        # Having run out of max_repeats it should not be active now.
        self.assertFalse(job.is_active)
Beispiel #5
0
    def test_on_job_executed_cb(self):

        data = {'runs': [], 'ctx': []}

        def get_context():
            ctx = {
                'name': rand_string(),
                'type': SCHEDULER.JOB_TYPE.INTERVAL_BASED
            }
            data['ctx'].append(ctx)
            return ctx

        def on_job_executed_cb(ctx):
            data['runs'].append(ctx)

        test_wait_time = 0.5
        job_sleep_time = 0.1
        job_max_repeats = 10

        job = Job(rand_int(),
                  'a',
                  SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                  Interval(seconds=0.1),
                  max_repeats=job_max_repeats)
        job.wait_sleep_time = job_sleep_time
        job.get_context = get_context

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                  timedelta(seconds=test_wait_time))
        scheduler.on_job_executed_cb = on_job_executed_cb

        scheduler.create(job, spawn=False)
        scheduler.run()

        self.assertEquals(len(data['runs']), len(data['ctx']))

        for idx, item in enumerate(data['runs']):
            self.assertEquals(data['ctx'][idx], item)
Beispiel #6
0
    def test_on_max_repeats_reached(self):

        test_wait_time = 0.5
        job_sleep_time = 0.02
        job_max_repeats = 3

        data = {'job':None, 'called':0}

        job = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=0.1), max_repeats=job_max_repeats)
        job.wait_sleep_time = job_sleep_time

        # Just to make sure it's inactive by default.
        self.assertTrue(job.is_active)

        scheduler = Scheduler(dummy_callback)
        data['old_on_max_repeats_reached'] = scheduler.on_max_repeats_reached

        def on_max_repeats_reached(job):
            data['job'] = job
            data['called'] += 1
            data['old_on_max_repeats_reached'](job)

        scheduler.on_max_repeats_reached = on_max_repeats_reached
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))

        scheduler.create(job)
        scheduler.run()

        now = datetime.utcnow()

        # Now the job should have reached the max_repeats limit within the now - test_wait_time period.
        self.assertIs(job, data['job'])
        self.assertEquals(1, data['called'])
        self.assertTrue(job.max_repeats_reached)
        self.assertFalse(job.keep_running)
        self.assertTrue(job.max_repeats_reached_at < now)
        self.assertTrue(job.max_repeats_reached_at >= now + timedelta(seconds=-test_wait_time))

        # Having run out of max_repeats it should not be active now.
        self.assertFalse(job.is_active)
Beispiel #7
0
    def test_edit(self):

        def callback():
            pass

        def on_max_repeats_reached_cb():
            pass

        start_time = datetime.utcnow()
        test_wait_time = 0.5
        job_interval1, job_interval2 = 2, 3
        job_sleep_time = 10
        job_max_repeats1, job_max_repeats2 = 20, 30

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))

        def check(scheduler, job, label):
            self.assertIn(job.name, scheduler.job_greenlets)
            self.assertIn(job, scheduler.jobs)

            self.assertEquals(1, len(scheduler.job_greenlets))
            self.assertEquals(1, len(scheduler.jobs))

            self.assertIs(job.run.im_func, scheduler.job_greenlets.values()[0]._run.im_func)

            clone = list(scheduler.jobs)[0]

            for name in 'name', 'interval', 'cb_kwargs', 'max_repeats', 'is_active':
                expected = getattr(job, name)
                given = getattr(clone, name)
                self.assertEquals(expected, given, '{} != {} ({})'.format(expected, given, name))

            job_cb = job.callback
            clone_cb = clone.callback

            job_on_max_cb = job.on_max_repeats_reached_cb
            clone_on_max_cb = clone.on_max_repeats_reached_cb

            if label == 'first':
                self.assertEquals(job.start_time, clone.start_time)
                self.assertIs(job_cb.im_func, clone_cb.im_func)
                self.assertIs(job_on_max_cb.im_func, clone_on_max_cb.im_func)

            else:
                self.assertEquals(job.start_time, clone.start_time)
                self.assertIs(clone_cb.im_func, scheduler.on_job_executed.im_func)
                self.assertIs(clone_on_max_cb.im_func, scheduler.on_max_repeats_reached.im_func)

        job1 = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=job_interval1), start_time, max_repeats=job_max_repeats1)
        job1.callback = callback
        job1.on_max_repeats_reached_cb = on_max_repeats_reached_cb
        job1.wait_sleep_time = job_sleep_time

        job2 = Job(rand_int(), 'a', SCHEDULER.JOB_TYPE.INTERVAL_BASED, Interval(seconds=job_interval2), start_time, max_repeats=job_max_repeats2)
        job2.callback = callback
        job2.on_max_repeats_reached_cb = on_max_repeats_reached_cb
        job2.wait_sleep_time = job_sleep_time

        scheduler.run()
        scheduler.create(job1)

        sleep(test_wait_time)

        # We have only job1 at this point
        check(scheduler, job1, 'first')

        # Removes job1 along the way ..
        scheduler.edit(job2)

        # .. so now job2 is the now removed job1.
        check(scheduler, job2, 'second')
Beispiel #8
0
    def test_run(self):

        test_wait_time = 0.3
        sched_sleep_time = 0.1

        data = {'sleep': [], 'jobs':set()}

        def _sleep(value):
            data['sleep'].append(value)

        def spawn_job(job):
            data['jobs'].add(job)

        def job_run(self):
            pass

        job1, job2, job3 = [get_job(str(x)) for x in range(3)]

        # Already run out of max_repeats and should not be started
        job4 = Job(rand_int(), rand_string(), SCHEDULER.JOB_TYPE.INTERVAL_BASED, start_time=parse('1997-12-23 21:24:27'),
            interval=Interval(seconds=5), max_repeats=3)

        job1.run = job_run
        job2.run = job_run
        job3.run = job_run
        job4.run = job_run

        scheduler = Scheduler(dummy_callback)
        scheduler.spawn_job = spawn_job
        scheduler.lock = RLock()
        scheduler.sleep = _sleep
        scheduler.sleep_time = sched_sleep_time
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() + timedelta(seconds=test_wait_time))

        scheduler.create(job1, spawn=False)
        scheduler.create(job2, spawn=False)
        scheduler.create(job3, spawn=False)
        scheduler.create(job4, spawn=False)

        scheduler.run()

        self.assertEquals(3, len(data['jobs']))
        self.assertTrue(scheduler.lock.called)

        for item in data['sleep']:
            self.assertEquals(sched_sleep_time, item)

        for job in job1, job2, job3:
            self.assertIn(job, data['jobs'])

        self.assertNotIn(job4, data['jobs'])
Beispiel #9
0
    def test_create(self):

        data = {'spawned_jobs': 0}

        def on_job_executed(*ignored):
            pass

        def job_run(*ignored):
            pass

        def spawn(func):
            self.assertIs(func, job_run)
            data['spawned_jobs'] += 1

        scheduler = Scheduler(dummy_callback)
        scheduler.on_job_executed = on_job_executed
        scheduler.lock = RLock()

        job1 = get_job()
        job1.run = job_run

        job2 = get_job()
        job2.run = job_run

        job3 = get_job(name=job2.name)
        job3.run = job_run

        job4 = get_job()
        job5 = get_job()

        job6 = get_job()
        job6.is_active = False

        with patch('gevent.spawn', spawn):
            scheduler.create(job1)
            scheduler.create(job2)

            # These two won't be added because scheduler.jobs is a set hashed by a job's name.
            scheduler.create(job2)
            scheduler.create(job3)

            # The first one won't be spawned but the second one will.
            scheduler.create(job4, spawn=False)
            scheduler.create(job5, spawn=True)

            # Won't be added anywhere nor spawned because it's inactive.
            scheduler.create(job6)

            self.assertEquals(scheduler.lock.called, 7)
            self.assertEquals(len(scheduler.jobs), 5)

            self.assertIn(job1, scheduler.jobs)
            self.assertIn(job2, scheduler.jobs)

            self.assertIs(job1.callback, scheduler.on_job_executed)
            self.assertIs(job2.callback, scheduler.on_job_executed)

            self.assertEquals(data['spawned_jobs'], 4)
Beispiel #10
0
    def test_edit(self):
        def callback():
            pass

        def on_max_repeats_reached_cb():
            pass

        start_time = datetime.utcnow()
        test_wait_time = 0.5
        job_interval1, job_interval2 = 2, 3
        job_sleep_time = 10
        job_max_repeats1, job_max_repeats2 = 20, 30

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                  timedelta(seconds=test_wait_time))

        def check(scheduler, job, label):
            self.assertIn(job.name, scheduler.job_greenlets)
            self.assertIn(job, scheduler.jobs)

            self.assertEquals(1, len(scheduler.job_greenlets))
            self.assertEquals(1, len(scheduler.jobs))

            self.assertIs(job.run.im_func,
                          scheduler.job_greenlets.values()[0]._run.im_func)

            clone = list(scheduler.jobs)[0]

            for name in 'name', 'interval', 'cb_kwargs', 'max_repeats', 'is_active':
                expected = getattr(job, name)
                given = getattr(clone, name)
                self.assertEquals(
                    expected, given,
                    '{} != {} ({})'.format(expected, given, name))

            job_cb = job.callback
            clone_cb = clone.callback

            job_on_max_cb = job.on_max_repeats_reached_cb
            clone_on_max_cb = clone.on_max_repeats_reached_cb

            if label == 'first':
                self.assertEquals(job.start_time, clone.start_time)
                self.assertIs(job_cb.im_func, clone_cb.im_func)
                self.assertIs(job_on_max_cb.im_func, clone_on_max_cb.im_func)

            else:
                self.assertEquals(job.start_time, clone.start_time)
                self.assertIs(clone_cb.im_func,
                              scheduler.on_job_executed.im_func)
                self.assertIs(clone_on_max_cb.im_func,
                              scheduler.on_max_repeats_reached.im_func)

        job1 = Job(rand_int(),
                   'a',
                   SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                   Interval(seconds=job_interval1),
                   start_time,
                   max_repeats=job_max_repeats1)
        job1.callback = callback
        job1.on_max_repeats_reached_cb = on_max_repeats_reached_cb
        job1.wait_sleep_time = job_sleep_time

        job2 = Job(rand_int(),
                   'a',
                   SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                   Interval(seconds=job_interval2),
                   start_time,
                   max_repeats=job_max_repeats2)
        job2.callback = callback
        job2.on_max_repeats_reached_cb = on_max_repeats_reached_cb
        job2.wait_sleep_time = job_sleep_time

        scheduler.run()
        scheduler.create(job1)

        sleep(test_wait_time)

        # We have only job1 at this point
        check(scheduler, job1, 'first')

        # Removes job1 along the way ..
        scheduler.edit(job2)

        # .. so now job2 is the now removed job1.
        check(scheduler, job2, 'second')
Beispiel #11
0
    def test_job_greenlets(self):

        data = {'spawned': [], 'stopped': []}

        class FakeGreenlet(object):
            def __init__(self, run):
                self.run = self._run = run

            def kill(self, *args, **kwargs):
                data['stopped'].append([self, args, kwargs])

        def spawn(job):
            g = FakeGreenlet(job)
            data['spawned'].append(g)
            return g

        with patch('gevent.spawn', spawn):

            test_wait_time = 0.5
            job_sleep_time = 10
            job_max_repeats = 30

            job1 = Job(rand_int(),
                       'a',
                       SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                       Interval(seconds=0.1),
                       max_repeats=job_max_repeats)
            job1.wait_sleep_time = job_sleep_time

            job2 = Job(rand_int(),
                       'b',
                       SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                       Interval(seconds=0.1),
                       max_repeats=job_max_repeats)
            job2.wait_sleep_time = job_sleep_time

            scheduler = Scheduler(dummy_callback)
            scheduler.lock = RLock()
            scheduler.iter_cb = iter_cb
            scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                      timedelta(seconds=test_wait_time))

            scheduler.create(job1, spawn=False)
            scheduler.create(job2, spawn=False)
            scheduler.run()

            self.assertEquals(scheduler.job_greenlets[job1.name]._run,
                              job1.run)
            self.assertEquals(scheduler.job_greenlets[job2.name]._run,
                              job2.run)

            self.assertTrue(job1.keep_running)
            self.assertTrue(job2.keep_running)

            scheduler.unschedule(job1)

            self.assertFalse(job1.keep_running)
            self.assertTrue(job2.keep_running)

            self.assertNotIn(job1.name, scheduler.job_greenlets)
            self.assertEquals(scheduler.job_greenlets[job2.name]._run,
                              job2.run)

            self.assertEquals(len(data['stopped']), 1)
            g, args, kwargs = data['stopped'][0]
            self.assertIs(g.run.im_func, job1.run.im_func
                          )  # That's how we know it was job1 deleted not job2
            self.assertIs(args, ())
            self.assertDictEqual(kwargs, {'timeout': 2.0})
Beispiel #12
0
    def test_delete(self):
        test_wait_time = 0.5
        job_sleep_time = 10
        job_max_repeats = 30

        job1 = Job(rand_int(),
                   'a',
                   SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                   Interval(seconds=0.1),
                   max_repeats=job_max_repeats)
        job1.wait_sleep_time = job_sleep_time

        job2 = Job(rand_int(),
                   'b',
                   SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                   Interval(seconds=0.1),
                   max_repeats=job_max_repeats)
        job2.wait_sleep_time = job_sleep_time

        scheduler = Scheduler(dummy_callback)
        scheduler.lock = RLock()
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                  timedelta(seconds=test_wait_time))

        scheduler.create(job1)
        scheduler.create(job2)
        scheduler.run()

        scheduler.unschedule(job1)

        self.assertIn(job2, scheduler.jobs)
        self.assertNotIn(job1, scheduler.jobs)
        self.assertFalse(job1.keep_running)

        # run - 1
        # create - 2
        # delete - 1
        # on_max_repeats_reached - 0 (because of how long it takes to run job_max_repeats with test_wait_time)
        # 1+2+1 = 4
        self.assertEquals(scheduler.lock.called, 4)
Beispiel #13
0
    def test_run(self):

        test_wait_time = 0.3
        sched_sleep_time = 0.1

        data = {'sleep': [], 'jobs': set()}

        def _sleep(value):
            data['sleep'].append(value)

        def spawn_job(job):
            data['jobs'].add(job)

        def job_run(self):
            pass

        job1, job2, job3 = [get_job(str(x)) for x in range(3)]

        # Already run out of max_repeats and should not be started
        job4 = Job(rand_int(),
                   rand_string(),
                   SCHEDULER.JOB_TYPE.INTERVAL_BASED,
                   start_time=parse('1997-12-23 21:24:27'),
                   interval=Interval(seconds=5),
                   max_repeats=3)

        job1.run = job_run
        job2.run = job_run
        job3.run = job_run
        job4.run = job_run

        scheduler = Scheduler(dummy_callback)
        scheduler.spawn_job = spawn_job
        scheduler.lock = RLock()
        scheduler.sleep = _sleep
        scheduler.sleep_time = sched_sleep_time
        scheduler.iter_cb = iter_cb
        scheduler.iter_cb_args = (scheduler, datetime.utcnow() +
                                  timedelta(seconds=test_wait_time))

        scheduler.create(job1, spawn=False)
        scheduler.create(job2, spawn=False)
        scheduler.create(job3, spawn=False)
        scheduler.create(job4, spawn=False)

        scheduler.run()

        self.assertEquals(3, len(data['jobs']))
        self.assertTrue(scheduler.lock.called)

        for item in data['sleep']:
            self.assertEquals(sched_sleep_time, item)

        for job in job1, job2, job3:
            self.assertIn(job, data['jobs'])

        self.assertNotIn(job4, data['jobs'])
Beispiel #14
0
    def test_create(self):

        data = {'spawned_jobs': 0}

        def on_job_executed(*ignored):
            pass

        def job_run(*ignored):
            pass

        def spawn(func):
            self.assertIs(func, job_run)
            data['spawned_jobs'] += 1

        scheduler = Scheduler(dummy_callback)
        scheduler.on_job_executed = on_job_executed
        scheduler.lock = RLock()

        job1 = get_job()
        job1.run = job_run

        job2 = get_job()
        job2.run = job_run

        job3 = get_job(name=job2.name)
        job3.run = job_run

        job4 = get_job()
        job5 = get_job()

        job6 = get_job()
        job6.is_active = False

        with patch('gevent.spawn', spawn):
            scheduler.create(job1)
            scheduler.create(job2)

            # These two won't be added because scheduler.jobs is a set hashed by a job's name.
            scheduler.create(job2)
            scheduler.create(job3)

            # The first one won't be spawned but the second one will.
            scheduler.create(job4, spawn=False)
            scheduler.create(job5, spawn=True)

            # Won't be added anywhere nor spawned because it's inactive.
            scheduler.create(job6)

            self.assertEquals(scheduler.lock.called, 7)
            self.assertEquals(len(scheduler.jobs), 5)

            self.assertIn(job1, scheduler.jobs)
            self.assertIn(job2, scheduler.jobs)

            self.assertIs(job1.callback, scheduler.on_job_executed)
            self.assertIs(job2.callback, scheduler.on_job_executed)

            self.assertEquals(data['spawned_jobs'], 4)