Ejemplo n.º 1
0
async def test_mergeable_do_not_merge_with_update_branch_immediately_waiting_for_checks(
) -> None:
    """
    merge.do_not_merge is only useful with merge.update_branch_immediately,
    Test when PR doesn't need update but is waiting for checks to finish.
    """
    mergeable = create_mergeable()
    api = create_api()
    pull_request = create_pull_request()
    config = create_config()
    branch_protection = create_branch_protection()
    context = create_context()
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    config.merge.do_not_merge = True
    config.merge.update_branch_immediately = True
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.context = "ci/test-api"
    context.state = StatusState.PENDING

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
    )
    assert api.set_status.called is True
    assert ("waiting for required status checks: {'ci/test-api'}"
            in api.set_status.calls[0]["msg"])

    assert api.update_branch.called is False
    assert api.queue_for_merge.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 2
0
async def test_mergeable_missing_requires_status_checks_failing_status_context(
) -> None:
    """
    If branch protection is enabled with requiresStatusChecks but a required check is failing we should not merge.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    context = create_context()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.context = "ci/test-api"
    context.state = StatusState.FAILURE

    await mergeable(
        api=api,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
        check_runs=[],
    )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 1
    assert ("failing required status checks: {'ci/test-api'}"
            in api.set_status.calls[0]["msg"])

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 3
0
async def test_mergeable_queue_in_progress() -> None:
    """
    If a PR has pending status checks or is behind, we still consider it eligible for merge and throw it in the merge queue.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()

    config.merge.optimistic_updates = False
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresStrictStatusChecks = True
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.state = StatusState.PENDING
    context.context = "ci/test-api"

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
    )
    assert api.set_status.call_count == 1
    assert "enqueued for merge" in api.set_status.calls[0]["msg"]
    assert api.dequeue.call_count == 0
    assert api.queue_for_merge.call_count == 1

    # verify we haven't tried to merge the PR
    assert api.merge.called is False
    assert api.update_branch.called is False
Ejemplo n.º 4
0
async def test_mergeable_travis_ci_checks() -> None:
    """
    GitHub has some weird, _undocumented_ logic for continuous-integration/travis-ci where "continuous-integration/travis-ci/{pr,push}" become "continuous-integration/travis-ci" in requiredStatusChecks.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    context = create_context()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = [
        "continuous-integration/travis-ci"
    ]
    context.state = StatusState.FAILURE
    context.context = "continuous-integration/travis-ci/pr"

    await mergeable(
        api=api,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
        check_runs=[],
    )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 1
    assert (
        "failing required status checks: {'continuous-integration/travis-ci/pr'}"
        in api.set_status.calls[0]["msg"])

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 5
0
async def test_mergeable_queue_in_progress_with_ready_to_merge() -> None:
    """
    If a PR has pending status checks or is behind, we still consider it eligible for merge and throw it in the merge queue.

    regression test to verify that with config.merge.prioritize_ready_to_merge =
    true we don't attempt to merge a PR directly but called queue_for_merge
    instead. If the status checks haven't passed or the branch needs an update
    it's not good to be merged directly, but it can be queued for the merge
    queue.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()

    config.merge.optimistic_updates = False
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresStrictStatusChecks = True
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.state = StatusState.PENDING
    context.context = "ci/test-api"
    config.merge.prioritize_ready_to_merge = True

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
    )
    assert api.set_status.call_count == 1
    assert "enqueued for merge" in api.set_status.calls[0]["msg"]
    assert api.dequeue.call_count == 0
    assert api.queue_for_merge.call_count == 1

    # verify we haven't tried to merge the PR
    assert api.merge.called is False
    assert api.update_branch.called is False
Ejemplo n.º 6
0
async def test_mergeable_skippable_contexts_merging_pull_request() -> None:
    """
    If a skippable check hasn't finished but we're merging, we need to raise an exception to retry for a short period of time to allow the check to finish. We won't retry forever because skippable checks will likely never finish.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()
    check_run = create_check_run()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"]
    config.merge.dont_wait_on_status_checks = ["WIP"]
    context.state = StatusState.PENDING
    context.context = "WIP"
    check_run.name = "ci/test-api"
    check_run.conclusion = CheckConclusionState.SUCCESS

    with pytest.raises(RetryForSkippableChecks):
        await mergeable(
            api=api,
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            check_runs=[check_run],
            contexts=[context],
            merging=True,
        )
    assert api.set_status.call_count == 1
    assert (
        "merging PR (waiting a bit for dont_wait_on_status_checks: ['WIP'])"
        in api.set_status.calls[0]["msg"])
    assert api.dequeue.call_count == 0

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 7
0
async def test_mergeable_skippable_check_timeout() -> None:
    """
    we wait for skippable checks when merging because it takes time for check statuses to be sent and acknowledged by GitHub. We time out after some time because skippable checks are likely to never complete. In this case we want to notify the user of this via status check.
    """
    mergeable = create_mergeable()
    api = create_api()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()
    check_run = create_check_run()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"]
    config.merge.dont_wait_on_status_checks = ["WIP"]
    context.state = StatusState.PENDING
    context.context = "WIP"
    check_run.name = "ci/test-api"
    check_run.conclusion = CheckConclusionState.SUCCESS

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        contexts=[context],
        check_runs=[check_run],
        merging=True,
        skippable_check_timeout=0,
    )

    assert api.set_status.called is True
    assert ("timeout reached for dont_wait_on_status_checks: ['WIP']"
            in api.set_status.calls[0]["msg"])
    assert api.update_branch.called is False
    assert api.queue_for_merge.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 8
0
async def test_mergeable_optimistic_update_wait_for_checks() -> None:
    """
    test merge.optimistic_updates when we don't need a branch update. Since merge.optimistic_updates is enabled we should wait_for_checks
    """
    mergeable = create_mergeable()
    api = create_api()
    config = create_config()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    context = create_context()

    config.merge.optimistic_updates = True
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStrictStatusChecks = True
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.state = StatusState.PENDING
    context.context = "ci/test-api"

    with pytest.raises(PollForever):
        await mergeable(
            api=api,
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            contexts=[context],
            #
            merging=True,
        )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 0
    assert api.update_branch.call_count == 0
    assert ("merging PR (waiting for status checks: {'ci/test-api'})"
            in api.set_status.calls[0]["msg"])

    # verify we haven't tried to merge the PR
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 9
0
async def test_mergeable_skippable_contexts_passing() -> None:
    """
    If a skippable check is passing we should queue the PR for merging
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()
    check_run = create_check_run()

    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"]
    config.merge.dont_wait_on_status_checks = ["WIP"]
    context.state = StatusState.SUCCESS
    context.context = "WIP"
    check_run.name = "ci/test-api"
    check_run.conclusion = CheckConclusionState.SUCCESS

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        check_runs=[check_run],
        contexts=[context],
    )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 0
    assert api.queue_for_merge.call_count == 1
    assert "enqueued for merge (position=4th)" in api.set_status.calls[0][
        "msg"]

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
Ejemplo n.º 10
0
async def test_mergeable_skippable_contexts_with_check_run() -> None:
    """
    If a skippable check hasn't finished, we shouldn't do anything.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()
    check_run = create_check_run()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["WIP", "ci/test-api"]
    config.merge.dont_wait_on_status_checks = ["WIP"]
    context.state = StatusState.SUCCESS
    context.context = "ci/test-api"
    check_run.name = "WIP"
    check_run.conclusion = None

    await mergeable(
        api=api,
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        check_runs=[check_run],
        contexts=[context],
    )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 0
    assert ("not waiting for dont_wait_on_status_checks: ['WIP']"
            in api.set_status.calls[0]["msg"])

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
Ejemplo n.º 11
0
async def test_mergeable_need_branch_update() -> None:
    """
    prioritize waiting for checks over branch updates when merging if merge.optimistic_updates is disabled.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    config = create_config()
    context = create_context()

    config.merge.optimistic_updates = False
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresStrictStatusChecks = True
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/test-api"]
    context.state = StatusState.PENDING
    context.context = "ci/test-api"

    with pytest.raises(PollForever):
        await mergeable(
            api=api,
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            contexts=[context],
            merging=True,
        )
    assert api.set_status.call_count == 1
    assert ("merging PR (waiting for status checks: {'ci/test-api'})"
            in api.set_status.calls[0]["msg"])
    assert api.dequeue.call_count == 0

    # verify we haven't tried to merge the PR
    assert api.merge.called is False
    assert api.queue_for_merge.called is False
    assert api.update_branch.called is False
Ejemplo n.º 12
0
async def test_mergeable_travis_ci_checks_success() -> None:
    """
    If continuous-integration/travis-ci/pr passes we shouldn't say we're waiting for continuous-integration/travis-ci.
    """
    api = create_api()
    mergeable = create_mergeable()
    pull_request = create_pull_request()
    branch_protection = create_branch_protection()
    context = create_context()

    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = [
        "continuous-integration/travis-ci",
        "ci/test-api",
    ]
    context.state = StatusState.SUCCESS
    context.context = "continuous-integration/travis-ci/pr"

    with pytest.raises(PollForever):
        await mergeable(
            api=api,
            pull_request=pull_request,
            branch_protection=branch_protection,
            contexts=[context],
            merging=True,
            check_runs=[],
        )
    assert api.set_status.call_count == 1
    assert api.dequeue.call_count == 0
    assert ("merging PR (waiting for status checks: {'ci/test-api'})"
            in api.set_status.calls[0]["msg"])

    # verify we haven't tried to update/merge the PR
    assert api.update_branch.called is False
    assert api.merge.called is False
    assert api.queue_for_merge.called is False