Ejemplo n.º 1
0
    def test_create_issue_on_fail(self, mock_get, mock_post, mock_patch):
        j = utils.create_job()
        get_data = [{"title": "foo", "number": 1}]
        mock_get.return_value = utils.Response(get_data)
        mock_post.return_value = utils.Response({"html_url": "<html_url>"})
        mock_patch.return_value = utils.Response({"html_url": "<html_url>"})

        git_config = utils.github_config(remote_update=True)
        with self.settings(INSTALLED_GITSERVERS=[git_config]):
            utils.update_job(j, status=models.JobStatus.SUCCESS)
            j.event.cause = models.Event.PULL_REQUEST
            j.event.save()

            # Don't do anything on a PR
            UpdateRemoteStatus.create_issue_on_fail(j)
            self.assertEqual(mock_get.call_count, 0)
            self.assertEqual(mock_post.call_count, 0)
            self.assertEqual(mock_patch.call_count, 0)

            j.event.cause = models.Event.PUSH
            j.event.save()
            # Don't do anything unless it is a failure
            UpdateRemoteStatus.create_issue_on_fail(j)
            self.assertEqual(mock_get.call_count, 0)
            self.assertEqual(mock_post.call_count, 0)
            self.assertEqual(mock_patch.call_count, 0)

            utils.update_job(j, status=models.JobStatus.FAILED)
            # Don't do anything unless the recipe wants to create an issue
            UpdateRemoteStatus.create_issue_on_fail(j)
            self.assertEqual(mock_get.call_count, 0)
            self.assertEqual(mock_post.call_count, 0)
            self.assertEqual(mock_patch.call_count, 0)

            j.recipe.create_issue_on_fail = True
            j.recipe.create_issue_on_fail_message = "Additional message"
            j.recipe.save()
            # Should create new issue
            UpdateRemoteStatus.create_issue_on_fail(j)
            self.assertEqual(mock_get.call_count, 1)
            self.assertEqual(mock_post.call_count, 1)
            self.assertEqual(mock_patch.call_count, 0)
Ejemplo n.º 2
0
    def test_create_event_summary(self, mock_get, mock_post):
        mock_get.return_value = utils.Response()
        mock_post.return_value = utils.Response()
        ev = utils.create_event()
        ev.comments_url = 'url'
        ev.save()
        j0 = utils.create_job(event=ev)
        config = utils.create_build_config("config1")
        j0.recipe.build_configs.add(config)
        utils.create_job(event=ev, recipe=j0.recipe, config=config)
        r1 = utils.create_recipe(name="r1")
        j1 = utils.create_job(recipe=r1, event=ev)
        j0.recipe.depends_on.add(r1)

        with self.settings(INSTALLED_GITSERVERS=[
                utils.github_config(post_event_summary=False,
                                    remote_update=True)
        ]):
            # Not posting the summary so we should not do anything
            UpdateRemoteStatus.create_event_summary(ev)
            self.assertEqual(mock_post.call_count, 0)
            self.assertEqual(mock_get.call_count, 0)

        with self.settings(INSTALLED_GITSERVERS=[
                utils.github_config(post_event_summary=True,
                                    remote_update=True)
        ]):
            # Configured to post the summary
            UpdateRemoteStatus.create_event_summary(ev)
            self.assertEqual(mock_post.call_count, 1)  # 1 for adding comment
            self.assertEqual(mock_get.call_count,
                             1)  # 1 for getting current comments

            utils.update_job(j1,
                             status=models.JobStatus.FAILED,
                             complete=True,
                             invalidated=True)
            utils.create_step_result(job=j1, status=models.JobStatus.FAILED)
            self.assertEqual(len(ev.get_unrunnable_jobs()), 2)
            UpdateRemoteStatus.create_event_summary(ev)
            self.assertEqual(mock_post.call_count, 2)
            self.assertEqual(mock_get.call_count, 2)
Ejemplo n.º 3
0
    def test_step_start_pr_status(self, mock_post):
        user = utils.get_test_user()
        job = utils.create_job(user=user)
        utils.update_job(job, status=models.JobStatus.CANCELED)
        results = utils.create_step_result(job=job)
        results.exit_status = 1
        results.save()

        job.event.cause = models.Event.PUSH
        job.event.save()

        with self.settings(
                INSTALLED_GITSERVERS=[utils.github_config(
                    remote_update=True)]):
            # Wrong cause
            UpdateRemoteStatus.step_start_pr_status(results, job)
            self.assertEqual(mock_post.call_count, 0)

            job.event.cause = models.Event.PULL_REQUEST
            job.event.save()

            # OK
            UpdateRemoteStatus.step_start_pr_status(results, job)
            self.assertEqual(mock_post.call_count, 1)
Ejemplo n.º 4
0
    def test_start_canceled_on_fail(self):
        user = utils.get_test_user()
        r0 = utils.create_recipe(name='recipe0', user=user)
        r1 = utils.create_recipe(name='recipe1', user=user)
        r2 = utils.create_recipe(name='recipe2', user=user)
        e0 = utils.create_event(user=user, cause=models.Event.PUSH)
        j0 = utils.create_job(recipe=r0, event=e0, user=user)
        utils.update_job(j0, status=models.JobStatus.SUCCESS, complete=True)
        j1 = utils.create_job(recipe=r1, event=e0, user=user)
        utils.update_job(j1, status=models.JobStatus.CANCELED, complete=True)
        j2 = utils.create_job(recipe=r2, event=e0, user=user)
        utils.update_job(j2, status=models.JobStatus.RUNNING, complete=True)
        e1 = utils.create_event(user=user,
                                cause=models.Event.PUSH,
                                commit1='12345')
        j3 = utils.create_job(recipe=r0, event=e1, user=user)
        utils.update_job(j3, status=models.JobStatus.SUCCESS, complete=True)
        j4 = utils.create_job(recipe=r1, event=e1, user=user)
        utils.update_job(j4, status=models.JobStatus.CANCELED, complete=True)
        j5 = utils.create_job(recipe=r2, event=e1, user=user)
        utils.update_job(j5, status=models.JobStatus.RUNNING, complete=True)

        e2 = utils.create_event(user=user,
                                cause=models.Event.PUSH,
                                commit1='123456')
        j6 = utils.create_job(recipe=r0, event=e2, user=user)
        utils.update_job(j6, status=models.JobStatus.SUCCESS, complete=True)
        j7 = utils.create_job(recipe=r1, event=e2, user=user)
        utils.update_job(j7, status=models.JobStatus.FAILED, complete=True)
        j8 = utils.create_job(recipe=r2, event=e2, user=user)
        utils.update_job(j8, status=models.JobStatus.FAILED_OK, complete=True)

        # If the job isn't a fail then it shouldn't do anything
        self.set_counts()
        UpdateRemoteStatus.start_canceled_on_fail(j6)
        self.compare_counts()

        # Normal behavior, a job fails and doesn't do anything to the previous event
        self.set_counts()
        UpdateRemoteStatus.start_canceled_on_fail(j7)
        self.compare_counts()

        repo_name = "%s/%s" % (e0.base.branch.repository.user.name,
                               e0.base.branch.repository.name)
        branch_name = e0.base.branch.name
        branch_settings = {
            "auto_cancel_push_events_except_current": True,
            "auto_uncancel_previous_event": True
        }
        repo_settings = {
            repo_name: {
                "branch_settings": {
                    branch_name: branch_settings
                }
            }
        }
        with self.settings(INSTALLED_GITSERVERS=[
                utils.github_config(repo_settings=repo_settings)
        ]):
            # If the job isn't a fail then it shouldn't do anything
            self.set_counts()
            UpdateRemoteStatus.start_canceled_on_fail(j3)
            self.compare_counts()

            # A job fails and should go to the previous event and uncancel any jobs
            self.set_counts()
            UpdateRemoteStatus.start_canceled_on_fail(j7)
            self.compare_counts(ready=1,
                                active_branches=1,
                                canceled=-1,
                                invalidated=1,
                                num_changelog=1,
                                num_jobs_completed=-1)
            j0.refresh_from_db()
            self.assertEqual(j0.status, models.JobStatus.SUCCESS)
            j1.refresh_from_db()
            self.assertEqual(j1.status, models.JobStatus.CANCELED)
            self.assertTrue(j1.complete)
            j2.refresh_from_db()
            self.assertEqual(j2.status, models.JobStatus.RUNNING)
            j3.refresh_from_db()
            self.assertEqual(j3.status, models.JobStatus.SUCCESS)
            j4.refresh_from_db()
            self.assertEqual(j4.status, models.JobStatus.NOT_STARTED)
            self.assertFalse(j4.complete)
            j5.refresh_from_db()
            self.assertEqual(j5.status, models.JobStatus.RUNNING)

            # If another job on the same event fails, it shouldn't try to uncancel previous events
            utils.update_job(j8, status=models.JobStatus.FAILED)
            utils.update_job(j4,
                             status=models.JobStatus.CANCELED,
                             complete=True)
            self.set_counts()
            UpdateRemoteStatus.start_canceled_on_fail(j7)
            self.compare_counts()
Ejemplo n.º 5
0
    def test_auto_uncancel_event(self):
        """
        E0 - starts running
        E1 - waits for E0 to finish
        E2 - waits for E0 to finish, cancels E1
            E0 is still running but clients start on E2 which gets
            a job failure. Uncancels E1.
        E3 - waits for E0 to finish. Cancels E2 and E1.
            At this point E3 will eventually fail. If E0 is
            still running then it needs to see that E2 is already failed so there is no need
            to uncancel. Instead, it needs to uncancel E1.
        """
        print("Branch: %s" % self.branch)
        c1_data, c2_data, push = self.create_data()
        push_rs = models.Recipe.objects.filter(cause=models.Recipe.CAUSE_PUSH)
        # remove the dependencies to make the testing easier
        for r in push_rs.all():
            r.depends_on.clear()
            r.auto_cancel_on_push = True
            r.save()
        self.assertEqual(push_rs.count(), 2)
        self.set_counts()
        push.save()
        self.compare_counts(events=1,
                            jobs=2,
                            ready=2,
                            active=2,
                            active_repos=1)
        e0 = models.Event.objects.latest()
        e0.status = models.JobStatus.RUNNING
        e0.complete = False
        e0.save()

        # We have an event that isn't running yet. So when a new event comes
        # in we can cancel it.
        # E0 is running
        for j in e0.jobs.all():
            utils.update_job(j, status=models.JobStatus.RUNNING)

        # A new event E1 comes in. E0 is still running so nothing is canceled.
        # Jobs on E1 can be run though
        c2_data.sha = '3'
        push.head_commit = c2_data
        self.set_counts()
        push.save()
        self.compare_counts(events=1, jobs=2, ready=2, commits=1, active=2)
        e0.refresh_from_db()
        for j in e0.jobs.all():
            self.assertEqual(j.status, models.JobStatus.RUNNING)

        e1 = models.Event.objects.latest()
        for j in e1.jobs.all():
            self.assertEqual(j.status, models.JobStatus.NOT_STARTED)
            self.assertEqual(j.ready, True)

        # A new event E2 comes in. E0 is still running so we need to cancel E1.
        # Jobs on E2 can be run.
        c2_data.sha = '4'
        push.head_commit = c2_data
        self.set_counts()
        push.save()
        self.compare_counts(events=1,
                            jobs=2,
                            ready=2,
                            commits=1,
                            active=2,
                            active_branches=1,
                            canceled=2,
                            events_canceled=1,
                            num_events_completed=1,
                            num_jobs_completed=2,
                            num_changelog=2)
        e0.refresh_from_db()
        for j in e0.jobs.all():
            self.assertEqual(j.status, models.JobStatus.RUNNING)

        e1.refresh_from_db()
        for j in e1.jobs.all():
            self.assertEqual(j.status, models.JobStatus.CANCELED)
            self.assertEqual(j.complete, True)

        e2 = models.Event.objects.latest()
        for j in e2.jobs.all():
            self.assertEqual(j.status, models.JobStatus.NOT_STARTED)
            self.assertEqual(j.complete, False)

        # Make a job failure on E2. Should uncancel E1.
        e2_j0 = e2.jobs.first()
        e2_j1 = e2.jobs.last()
        utils.update_job(e2_j0, status=models.JobStatus.FAILED, complete=True)
        self.set_counts()
        UpdateRemoteStatus.start_canceled_on_fail(e2_j0)
        self.compare_counts(
            active_branches=-1,
            canceled=-2,
            events_canceled=-1,
            invalidated=2,
            num_changelog=2,
            num_events_completed=-1,
            num_jobs_completed=-2,
        )
        e0.refresh_from_db()
        for j in e0.jobs.all():
            self.assertEqual(j.status, models.JobStatus.RUNNING)

        e1.refresh_from_db()
        for j in e1.jobs.all():
            self.assertEqual(j.status, models.JobStatus.NOT_STARTED)
            self.assertEqual(j.complete, False)

        # A new event E3 comes in. E0, E1, and E2 all are running
        # so we need to cancel E1 and E2.
        # Jobs on E3 can be run.
        c2_data.sha = '5'
        push.head_commit = c2_data
        self.set_counts()
        push.save()
        self.compare_counts(events=1,
                            jobs=2,
                            ready=2,
                            commits=1,
                            active=2,
                            active_branches=1,
                            canceled=3,
                            events_canceled=1,
                            num_events_completed=2,
                            num_jobs_completed=3,
                            num_changelog=3)

        e0.refresh_from_db()
        for j in e0.jobs.all():
            self.assertEqual(j.status, models.JobStatus.RUNNING)

        e1.refresh_from_db()
        for j in e1.jobs.all():
            self.assertEqual(j.status, models.JobStatus.CANCELED)
            self.assertEqual(j.complete, True)

        e2.refresh_from_db()
        e2_j0.refresh_from_db()
        self.assertEqual(e2_j0.status, models.JobStatus.FAILED)
        self.assertEqual(e2_j0.complete, True)
        e2_j1.refresh_from_db()
        self.assertEqual(e2_j1.status, models.JobStatus.CANCELED)
        self.assertEqual(e2_j1.complete, True)

        e3 = models.Event.objects.latest()
        for j in e3.jobs.all():
            self.assertEqual(j.status, models.JobStatus.NOT_STARTED)
            self.assertEqual(j.complete, False)

        # Make a job failure on E3. Should uncancel E1 and leave E2 canceled.
        e3_j0 = e3.jobs.first()
        utils.update_job(e3_j0, status=models.JobStatus.FAILED, complete=True)
        self.set_counts()
        UpdateRemoteStatus.start_canceled_on_fail(e3_j0)
        self.compare_counts(
            active_branches=-1,
            canceled=-2,
            events_canceled=-1,
            num_changelog=2,
            num_events_completed=-1,
            num_jobs_completed=-2,
        )
        e0.refresh_from_db()
        for j in e0.jobs.all():
            self.assertEqual(j.status, models.JobStatus.RUNNING)

        e1.refresh_from_db()
        for j in e1.jobs.all():
            self.assertEqual(j.status, models.JobStatus.NOT_STARTED)
            self.assertEqual(j.complete, False)

        e2.refresh_from_db()
        e2_j0.refresh_from_db()
        self.assertEqual(e2_j0.status, models.JobStatus.FAILED)
        self.assertEqual(e2_j0.complete, True)
        e2_j1.refresh_from_db()
        self.assertEqual(e2_j1.status, models.JobStatus.CANCELED)
        self.assertEqual(e2_j1.complete, True)
Ejemplo n.º 6
0
    def test_sync_badges(self):
        # Nothing configured
        out = StringIO()
        with self.settings(INSTALLED_GITSERVERS=[
                utils.github_config(repo_settings={"owner/repo": {}})
        ]):
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts()

        with self.settings(INSTALLED_GITSERVERS=[
                utils.github_config(repo_settings={
                    "owner/repo": {
                        "badges": [{
                            "recipe": "foo",
                            "name": "badge"
                        }]
                    }
                })
        ]):
            # Does not match any recipes
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts()

            # Match but no jobs
            r = models.Recipe.objects.first()
            r.filename = "foo"
            r.save()
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts()

            j = utils.create_job(recipe=r, )
            utils.update_job(j, status=models.JobStatus.FAILED_OK)
            j.event.cause = models.Event.PUSH
            j.event.save()
            j.save()

            # Should create a new badge
            self.set_counts()
            management.call_command("sync_badges", "--dryrun", stdout=out)
            self.compare_counts()
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts(badges=1)

            # doing it again shouldn't change anything
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts()

            # Now it should delete the one we just created since it no longer matches
            r.filename = "bar"
            r.save()
            self.set_counts()
            management.call_command("sync_badges", "--dryrun", stdout=out)
            self.compare_counts()
            self.set_counts()
            management.call_command("sync_badges", stdout=out)
            self.compare_counts(badges=-1)
Ejemplo n.º 7
0
    def test_cancel_old_jobs(self, mock_post, mock_get):
        out = StringIO()
        with self.assertRaises(CommandError):
            management.call_command("cancel_old_jobs", stdout=out)

        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs",
                                "--dryrun",
                                "--days",
                                "1",
                                stdout=out)
        self.compare_counts()
        self.assertIn("No jobs to cancel", out.getvalue())

        j = utils.create_job()
        created = TimeUtils.get_local_time() - timedelta(days=2)
        utils.update_job(j,
                         ready=True,
                         active=True,
                         status=models.JobStatus.NOT_STARTED,
                         created=created,
                         complete=False)

        # Make sure dryrun doesn't change anything
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs",
                                "--dryrun",
                                "--days",
                                "1",
                                stdout=out)
        self.compare_counts()
        self.assertIn(str(j), out.getvalue())
        j.refresh_from_db()
        self.assertEqual(j.status, models.JobStatus.NOT_STARTED)

        # Should update the job and event status
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs", "--days", "1", stdout=out)
        self.compare_counts(active_branches=1,
                            canceled=1,
                            events_canceled=1,
                            num_changelog=1,
                            num_events_completed=1,
                            num_jobs_completed=1)
        self.assertIn(str(j), out.getvalue())
        j.refresh_from_db()
        j.event.refresh_from_db()
        self.assertTrue(j.complete)
        self.assertEqual(j.status, models.JobStatus.CANCELED)
        self.assertEqual(j.event.status, models.JobStatus.CANCELED)
        self.assertTrue(j.event.complete)

        # Should not change anything since it isn't old enough
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False)
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs", "--days", "3", stdout=out)
        self.compare_counts()
        self.assertIn("No jobs to cancel", out.getvalue())
        self.assertNotIn(str(j), out.getvalue())
        j.refresh_from_db()
        self.assertEqual(j.status, models.JobStatus.NOT_STARTED)

        # Should update the job and event status
        created = TimeUtils.get_local_time() - timedelta(hours=2)
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False,
                         created=created)
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs", "--hours", "1", stdout=out)
        self.compare_counts(canceled=1, num_changelog=1, num_jobs_completed=1)
        self.assertIn(str(j), out.getvalue())
        j.refresh_from_db()
        self.assertEqual(j.status, models.JobStatus.CANCELED)

        # Should not change anything since it isn't old enough
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False,
                         created=created)
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs", "--hours", "3", stdout=out)
        self.compare_counts()
        self.assertIn("No jobs to cancel", out.getvalue())
        self.assertNotIn(str(j), out.getvalue())
        j.refresh_from_db()
        self.assertEqual(j.status, models.JobStatus.NOT_STARTED)

        # Make sure setting allowed to fail works
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False,
                         created=created)
        out = StringIO()
        self.set_counts()
        management.call_command("cancel_old_jobs",
                                "--hours",
                                "1",
                                "--allowed-fail",
                                stdout=out)
        self.compare_counts(events_canceled=-1,
                            num_changelog=1,
                            num_jobs_completed=1)
        self.assertIn(str(j), out.getvalue())
        j.refresh_from_db()
        self.assertEqual(j.status, models.JobStatus.FAILED_OK)

        # Check the --client-runner-user option only accepts <host>:<user> syntax
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False,
                         created=created)
        out = StringIO()
        self.set_counts()
        with self.assertRaises(CommandError):
            management.call_command("cancel_old_jobs",
                                    "--hours",
                                    "1",
                                    '--client-runner-user',
                                    'foo',
                                    stdout=out)
        self.compare_counts()

        # Valid --client-runner-user
        self.set_counts()
        management.call_command(
            "cancel_old_jobs",
            "--hours",
            "1",
            '--client-runner-user',
            "%s:%s" %
            (j.recipe.build_user.server.name, j.recipe.build_user.name),
            stdout=out)
        self.compare_counts(canceled=1,
                            num_changelog=1,
                            num_jobs_completed=1,
                            events_canceled=1)

        # --client-runner-user with no jobs
        utils.update_job(j,
                         status=models.JobStatus.NOT_STARTED,
                         complete=False,
                         created=created)
        other_user = utils.create_user(name="other_user")
        self.set_counts()
        management.call_command("cancel_old_jobs",
                                "--hours",
                                "1",
                                '--client-runner-user',
                                "%s:%s" %
                                (other_user.server.name, other_user.name),
                                stdout=out)
        self.compare_counts()
Ejemplo n.º 8
0
    def test_ready_jobs_client(self):
        user = utils.get_test_user()
        client = utils.create_client()
        request = self.factory.get('/')
        client_ip = views.get_client_ip(request)
        client.ip = client_ip
        client.save()
        url = reverse('ci:client:ready_jobs',
                      args=[user.build_key, client.name])
        r0 = utils.create_recipe(name='recipe0', user=user)
        r1 = utils.create_recipe(name='recipe1', user=user)
        j0 = utils.create_job(user=user, recipe=r0)
        j1 = utils.create_job(user=user, recipe=r1)
        utils.update_job(j0,
                         ready=True,
                         complete=False,
                         status=models.JobStatus.RUNNING,
                         client=client)
        utils.update_job(j1,
                         ready=True,
                         active=True,
                         status=models.JobStatus.NOT_STARTED)
        # we have a client trying to get ready jobs but
        # there is a job that is in the RUNNING state
        # associated with that client. That must mean
        # that the client previously stopped without letting the
        # server know, so the previous job should get
        # canceled
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts(canceled=1,
                            num_jobs_completed=1,
                            num_changelog=1,
                            active_branches=1)
        self.assertEqual(response.status_code, 200)
        j0.refresh_from_db()
        self.assertEqual(j0.status, models.JobStatus.CANCELED)

        # if all jobs are in running, the event should be canceled
        utils.update_job(j0, complete=False, status=models.JobStatus.RUNNING)
        utils.update_job(j1,
                         complete=False,
                         status=models.JobStatus.RUNNING,
                         client=client)
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts(canceled=2,
                            num_jobs_completed=2,
                            num_changelog=2,
                            events_canceled=1,
                            num_events_completed=1)
        self.assertEqual(response.status_code, 200)
        j0.refresh_from_db()
        self.assertEqual(j0.status, models.JobStatus.CANCELED)
        j1.refresh_from_db()
        self.assertEqual(j1.status, models.JobStatus.CANCELED)

        # Try again, nothing should change
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts()
        self.assertEqual(response.status_code, 200)
Ejemplo n.º 9
0
    def test_ready_jobs(self):
        url = reverse('ci:client:ready_jobs', args=['123', 'client'])
        # only get allowed
        self.set_counts()
        response = self.client.post(url)
        self.compare_counts()
        self.assertEqual(response.status_code, 405)  # not allowed
        self.compare_counts()

        # valid request, but no user with build key, so no jobs
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts(num_clients=1)
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIn('jobs', data)
        self.assertEqual(len(data['jobs']), 0)

        user = utils.get_test_user()
        job = utils.create_job(user=user)
        job.ready = True
        job.active = True
        job.save()
        r2 = utils.create_recipe(name='recipe2', user=user)
        r3 = utils.create_recipe(name='recipe3', user=user)
        r4 = utils.create_recipe(name='recipe4', user=user)
        job2 = utils.create_job(recipe=r2, user=user)
        job3 = utils.create_job(recipe=r3, user=user)
        job4 = utils.create_job(recipe=r4, user=user)
        utils.update_job(job2, ready=True, active=True)
        utils.update_job(job3, ready=True, active=True)
        utils.update_job(job4, ready=True, active=True)
        r2.priority = 10
        r2.save()
        r3.priority = 5
        r3.save()
        job.recipe.priority = 1
        job.recipe.save()
        r4.priority = 1
        r4.save()

        # valid request with a ready job
        url = reverse('ci:client:ready_jobs', args=[user.build_key, 'client'])
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts()
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIn('jobs', data)
        self.assertEqual(len(data['jobs']), 4)
        self.assertEqual(data['jobs'][0]['id'], job2.pk)
        self.assertEqual(data['jobs'][1]['id'], job3.pk)
        # two jobs with the same priorty, the one created first should run first
        self.assertEqual(data['jobs'][2]['id'], job.pk)
        self.assertEqual(data['jobs'][3]['id'], job4.pk)

        # Test to see if client_runner_user is working
        # The original user should now only have 3 jobs
        # and the new user that is the client_runner_user
        # should have 1
        other_user = utils.create_user("other_user")
        r4.client_runner_user = other_user
        r4.save()
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts()
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIn('jobs', data)
        self.assertEqual(len(data['jobs']), 3)

        url = reverse('ci:client:ready_jobs',
                      args=[other_user.build_key, 'client'])
        self.set_counts()
        response = self.client.get(url)
        self.compare_counts()
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIn('jobs', data)
        self.assertEqual(len(data['jobs']), 1)