Example #1
0
def test_as_dict(mock_revision, mock_hgmo):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot import Reliability
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        "clang-tidy",
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
        Reliability.Low,
    )

    assert issue.as_dict() == {
        "analyzer": "clang-tidy",
        "path": "test.cpp",
        "line": 42,
        "nb_lines": 1,
        "column": 51,
        "check": "dummy-check",
        "level": "warning",
        "message": "dummy message withUppercaseChars",
        "in_patch": False,
        "validates": True,
        "publishable": False,
        "hash": "b0f6aa535682909e46e48c783a5737d4",
    }
def test_phabricator_clang_tidy(mock_phabricator, phab, mock_try_task, mock_task):
    """
    Test Phabricator reporter publication on a mock clang-tidy issue
    """

    with mock_phabricator as api:
        revision = Revision.from_try(mock_try_task, api)
        revision.mercurial_revision = "deadbeef1234"
        revision.repository = "https://hg.mozilla.org/try"
        revision.repository_try_name = "try"
        revision.lines = {
            # Add dummy lines diff
            "another_test.cpp": [41, 42, 43]
        }
        revision.files = ["another_test.cpp"]
        reporter = PhabricatorReporter({"analyzers": ["clang-tidy"]}, api=api)

    issue = ClangTidyIssue(
        mock_task(ClangTidyTask, "source-test-clang-tidy"),
        revision,
        "another_test.cpp",
        "42",
        "51",
        "modernize-use-nullptr",
        "dummy message",
    )
    assert issue.is_publishable()

    issues, patches = reporter.publish([issue], revision, [])
    assert len(issues) == 1
    assert len(patches) == 0

    # Check the comment has been posted
    assert phab.comments[51] == [VALID_CLANG_TIDY_MESSAGE]
Example #3
0
def test_as_dict(mock_revision, mock_hgmo):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot import Reliability
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        "clang-tidy",
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
        "error",
        Reliability.Low,
    )

    assert issue.as_dict() == {
        "analyzer": "clang-tidy",
        "path": "test.cpp",
        "line": 42,
        "nb_lines": 1,
        "column": 51,
        "check": "dummy-check",
        "level": "error",
        "message": "dummy message withUppercaseChars",
        "in_patch": False,
        "validates": True,
        "publishable": False,
        "hash": "f94457942070fa51a52230f35a488be0",
    }
def test_phabricator_clang_tidy_build_error(
    mock_phabricator, phab, mock_try_task, mock_task
):
    """
    Test Phabricator Lint for a ClangTidyIssue with build error
    """

    from code_review_bot import Level

    with mock_phabricator as api:
        revision = Revision.from_try(mock_try_task, api)
        revision.lines = {
            # Add dummy lines diff
            "test.cpp": [41, 42, 43]
        }
        revision.build_target_phid = "PHID-HMBD-deadbeef12456"
        revision.repository = "https://hg.mozilla.org/try"
        revision.repository_try_name = "try"
        revision.mercurial_revision = "deadbeef1234"

        reporter = PhabricatorReporter({}, api=api)

        issue = ClangTidyIssue(
            analyzer=mock_task(ClangTidyTask, "mock-clang-tidy"),
            revision=revision,
            path="dom/animation/Animation.cpp",
            line=57,
            column=46,
            check="clang-diagnostic-error",
            level=Level.Error,
            message="Some Error Message",
            publish=True,
        )

        assert issue.is_publishable()

        issues, patches = reporter.publish([issue], revision, [])
        assert len(issues) == 1
        assert len(patches) == 0

        # Check the callback has been used
        assert phab.build_messages["PHID-HMBD-deadbeef12456"] == [
            {
                "buildTargetPHID": "PHID-HMBD-deadbeef12456",
                "lint": [
                    {
                        "code": "clang-diagnostic-error",
                        "description": "(IMPORTANT) ERROR: Some Error Message",
                        "line": 57,
                        "char": 46,
                        "name": "Build Error",
                        "path": "dom/animation/Animation.cpp",
                        "severity": "error",
                    }
                ],
                "type": "work",
                "unit": [],
            }
        ]
        assert phab.comments[51] == [VALID_BUILD_ERROR_MESSAGE]
Example #5
0
def test_as_dict(mock_revision, mock_hgmo, mock_task):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot import Level
    from code_review_bot import Reliability

    issue = ClangTidyIssue(
        mock_task(ClangTidyTask, "clang-tidy"),
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
        Level.Warning,
        Reliability.Low,
    )

    assert issue.as_dict() == {
        "analyzer": "clang-tidy",
        "path": "test.cpp",
        "line": 42,
        "nb_lines": 1,
        "column": 51,
        "check": "dummy-check",
        "level": "warning",
        "message": "dummy message withUppercaseChars",
        "in_patch": False,
        "validates": True,
        "publishable": False,
        "hash": "f434c3f44cd5da419d9119f504086513",
        "fix": None,
    }
Example #6
0
def test_expanded_macros(mock_revision):
    """
    Test expanded macros are detected by clang issue
    """
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        "clang-tidy",
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy message",
        "dummy-check",
        "error",
    )
    assert issue.is_problem()
    assert issue.line == 42
    assert issue.column == 51
    assert issue.notes == []
    assert issue.is_expanded_macro() is False

    # Add a note starting with "expanded from macro..."
    issue.notes.append(
        ClangTidyIssue(
            "clang-tidy",
            mock_revision,
            "test.cpp",
            "42",
            "51",
            "dummy-check-note",
            "expanded from macro Blah dummy.cpp",
            "note",
        )
    )
    assert issue.is_expanded_macro() is True

    # Add another note does not change it
    issue.notes.append(
        ClangTidyIssue(
            "clang-tidy",
            mock_revision,
            "test.cpp",
            "42",
            "51",
            "dummy-check-note",
            "This is not an expanded macro",
            "note",
        )
    )
    assert issue.is_expanded_macro() is True

    # But if we swap them, it does not work anymore
    issue.notes.reverse()
    assert issue.is_expanded_macro() is False
Example #7
0
def test_as_markdown(mock_revision):
    """
    Test markdown generation for ClangTidyIssue
    """
    from code_review_bot import Reliability
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message",
        "error",
        Reliability.High,
    )
    issue.body = "Dummy body"

    assert (issue.as_markdown() == """
## clang-tidy error

- **Message**: dummy message
- **Location**: test.cpp:42:51
- **In patch**: no
- **Clang check**: dummy-check
- **Publishable check**: no
- **Third Party**: no
- **Expanded Macro**: no
- **Publishable **: no
- **Is new**: no
- **Checker reliability **: high (false positive risk)

```
Dummy body
```


""")
    assert issue.as_phabricator_lint() == {
        "char": 51,
        "code": "clang-tidy.dummy-check",
        "line": 42,
        "name": "Clang-Tidy - dummy-check",
        "description":
        "dummy message\nChecker reliability is high, meaning that the false positive ratio is low.\n\n > Dummy body",
        "path": "test.cpp",
        "severity": "warning",
    }
Example #8
0
    def _test_reporter(api, analyzers_skipped):
        # Always use the same setup, only varies the analyzers
        revision = Revision.from_try(mock_try_task, api)
        revision.lines = {"test.cpp": [0, 41, 42, 43], "dom/test.cpp": [42]}
        reporter = PhabricatorReporter(
            {"analyzers_skipped": analyzers_skipped}, api=api
        )

        issues = [
            ClangFormatIssue("mock-clang-format", "dom/test.cpp", 42, 1, revision),
            ClangTidyIssue(
                "mock-clang-tidy",
                revision,
                "test.cpp",
                "42",
                "51",
                "modernize-use-nullptr",
                "dummy message",
            ),
            InferIssue(
                "mock-infer",
                {
                    "file": "test.cpp",
                    "line": 42,
                    "column": 1,
                    "bug_type": "dummy",
                    "kind": "WARNING",
                    "qualifier": "dummy message.",
                },
                revision,
            ),
            MozLintIssue(
                "mock-lint-flake8",
                "test.cpp",
                1,
                "error",
                42,
                "flake8",
                "Python error",
                "EXXX",
                revision,
            ),
            CoverageIssue("test.cpp", 0, "This file is uncovered", revision),
        ]

        assert all(i.is_publishable() for i in issues)

        revision.improvement_patches = [
            ImprovementPatch("dummy", repr(revision), "Whatever"),
            ImprovementPatch("mock-clang-tidy", repr(revision), "Some C fixes"),
            ImprovementPatch("mock-clang-format", repr(revision), "Some lint fixes"),
            ImprovementPatch("mock-infer", repr(revision), "Some java fixes"),
            ImprovementPatch("mock-lint-flake8", repr(revision), "Some js fixes"),
        ]
        list(
            map(lambda p: p.write(), revision.improvement_patches)
        )  # trigger local write

        return reporter.publish(issues, revision, [])
Example #9
0
def test_as_text(mock_revision, mock_task):
    """
    Test text export for ClangTidyIssue
    """

    issue = ClangTidyIssue(
        mock_task(ClangTidyTask, "clang-tidy"),
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
    )

    assert (issue.as_text(
    ) == "Warning: Dummy message withUppercaseChars [clang-tidy: dummy-check]")
Example #10
0
def test_as_markdown(mock_revision, mock_task):
    """
    Test markdown generation for ClangTidyIssue
    """
    from code_review_bot import Level
    from code_review_bot import Reliability

    issue = ClangTidyIssue(
        mock_task(ClangTidyTask, "clang-tidy"),
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message",
        Level.Warning,
        Reliability.High,
    )

    assert (
        issue.as_markdown()
        == """
## clang-tidy warning

- **Message**: dummy message
- **Location**: test.cpp:42:51
- **In patch**: no
- **Clang check**: dummy-check
- **Publishable check**: yes
- **Expanded Macro**: no
- **Publishable **: no
- **Checker reliability **: high (false positive risk)


"""
    )
    assert issue.as_phabricator_lint() == {
        "char": 51,
        "code": "dummy-check",
        "line": 42,
        "name": "clang-tidy",
        "description": "WARNING: dummy message",
        "path": "test.cpp",
        "severity": "warning",
    }
Example #11
0
def test_as_text(mock_revision):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        "clang-tidy",
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
    )

    assert (issue.as_text(
    ) == "Warning: Dummy message withUppercaseChars [clang-tidy: dummy-check]")
Example #12
0
def test_as_dict(mock_revision):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot import Reliability
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
        "error",
        Reliability.Low,
    )
    issue.body = "Dummy body withUppercaseChars"

    assert issue.as_dict() == {
        "analyzer": "clang-tidy",
        "path": "test.cpp",
        "line": 42,
        "nb_lines": 1,
        "char": 51,
        "check": "dummy-check",
        "level": "error",
        "message": "dummy message withUppercaseChars",
        "body": "Dummy body withUppercaseChars",
        "reason": None,
        "notes": [],
        "validation": {
            "publishable_check": False,
            "third_party": False,
            "is_expanded_macro": False,
        },
        "in_patch": False,
        "is_new": False,
        "validates": False,
        "publishable": False,
        "reliability": "low",
    }
Example #13
0
def test_as_text(mock_revision):
    """
    Test text export for ClangTidyIssue
    """
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    issue = ClangTidyIssue(
        mock_revision,
        "test.cpp",
        "42",
        "51",
        "dummy-check",
        "dummy message withUppercaseChars",
        "error",
    )
    issue.body = "Dummy body withUppercaseChars"

    assert (
        issue.as_text() ==
        "Error: Dummy message withUppercaseChars [clang-tidy: dummy-check]\n```\nDummy body withUppercaseChars\n```"
    )
Example #14
0
def test_expanded_macros(mock_revision, mock_task):
    """
    Test expanded macros are detected by clang issue
    """
    analyzer = mock_task(ClangTidyTask, "clang-tidy")
    issue = ClangTidyIssue(
        analyzer, mock_revision, "test.cpp", "42", "51", "dummy message", "dummy-check"
    )
    assert issue.line == 42
    assert issue.column == 51
    assert issue.notes == []
    assert issue.is_expanded_macro() is False

    # Add a note starting with "expanded from macro..."
    issue.notes.append(
        ClangTidyIssue(
            analyzer,
            mock_revision,
            "test.cpp",
            "42",
            "51",
            "dummy-check-note",
            "expanded from macro Blah dummy.cpp",
        )
    )
    assert issue.is_expanded_macro() is True

    # Add another note does not change it
    issue.notes.append(
        ClangTidyIssue(
            analyzer,
            mock_revision,
            "test.cpp",
            "42",
            "51",
            "dummy-check-note",
            "This is not an expanded macro",
        )
    )
    assert issue.is_expanded_macro() is True

    # But if we swap them, it does not work anymore
    issue.notes.reverse()
    assert issue.is_expanded_macro() is False
Example #15
0
def mock_clang_tidy_issues(mock_revision, mock_task):
    """
    Build a list of clang-tidy issues
    """

    from code_review_bot import Level

    return [
        ClangTidyIssue(
            analyzer=mock_task(ClangTidyTask, "mock-clang-tidy"),
            revision=mock_revision,
            path="dom/animation/Animation.cpp",
            line=57,
            column=46,
            level=Level("error") if i % 2 else Level("warning"),
            check="clanck.checker",
            message="Some Error Message",
            publish=True,
        ) for i in range(2)
    ]
def test_phabricator_analyzers(
    analyzers_skipped,
    valid_issues,
    valid_patches,
    mock_config,
    mock_phabricator,
    mock_try_task,
    mock_task,
):
    """
    Test analyzers filtering on phabricator reporter
    """
    with mock_phabricator as api:

        # Skip commenting on phabricator
        # we only care about filtering issues
        api.comment = unittest.mock.Mock(return_value=True)

        # Always use the same setup, only varies the analyzers
        revision = Revision.from_try(mock_try_task, api)
        revision.mercurial_revision = "deadbeef1234"
        revision.repository = "https://hg.mozilla.org/try"
        revision.repository_try_name = "try"
        revision.lines = {"test.cpp": [0, 41, 42, 43], "dom/test.cpp": [42]}
        reporter = PhabricatorReporter(
            {"analyzers_skipped": analyzers_skipped}, api=api
        )

    issues = [
        ClangFormatIssue(
            mock_task(ClangFormatTask, "mock-clang-format"),
            "dom/test.cpp",
            [
                (41, 41, b"no change"),
                (42, None, b"deletion"),
                (None, 42, b"change here"),
            ],
            revision,
        ),
        ClangTidyIssue(
            mock_task(ClangTidyTask, "mock-clang-tidy"),
            revision,
            "test.cpp",
            "42",
            "51",
            "modernize-use-nullptr",
            "dummy message",
        ),
        InferIssue(
            mock_task(InferTask, "mock-infer"),
            {
                "file": "test.cpp",
                "line": 42,
                "column": 1,
                "bug_type": "dummy",
                "kind": "WARNING",
                "qualifier": "dummy message.",
            },
            revision,
        ),
        MozLintIssue(
            mock_task(MozLintTask, "mock-lint-flake8"),
            "test.cpp",
            1,
            "error",
            42,
            "flake8",
            "Python error",
            "EXXX",
            revision,
        ),
        CoverageIssue(
            mock_task(ZeroCoverageTask, "coverage"),
            "test.cpp",
            0,
            "This file is uncovered",
            revision,
        ),
    ]

    assert all(i.is_publishable() for i in issues)

    revision.improvement_patches = [
        ImprovementPatch(mock_task(DefaultTask, "dummy"), repr(revision), "Whatever"),
        ImprovementPatch(
            mock_task(ClangTidyTask, "mock-clang-tidy"), repr(revision), "Some C fixes"
        ),
        ImprovementPatch(
            mock_task(ClangFormatTask, "mock-clang-format"),
            repr(revision),
            "Some lint fixes",
        ),
        ImprovementPatch(
            mock_task(InferTask, "mock-infer"), repr(revision), "Some java fixes"
        ),
        ImprovementPatch(
            mock_task(MozLintTask, "mock-lint-flake8"), repr(revision), "Some js fixes"
        ),
    ]
    list(map(lambda p: p.write(), revision.improvement_patches))  # trigger local write

    issues, patches = reporter.publish(issues, revision, [])

    # Check issues & patches analyzers
    assert len(issues) == len(valid_issues)
    assert len(patches) == len(valid_patches)
    assert [i.analyzer.name for i in issues] == valid_issues
    assert [p.analyzer.name for p in patches] == valid_patches
def test_phabricator_clang_tidy_and_coverage(
    mock_config, mock_phabricator, phab, mock_try_task, mock_task
):
    """
    Test Phabricator reporter publication on a mock coverage issue
    """

    with mock_phabricator as api:
        revision = Revision.from_try(mock_try_task, api)
        revision.mercurial_revision = "deadbeef1234"
        revision.repository = "https://hg.mozilla.org/try"
        revision.repository_try_name = "try"
        revision.lines = {
            # Add dummy lines diff
            "test.txt": [0],
            "path/to/test.cpp": [0],
            "another_test.cpp": [41, 42, 43],
        }
        revision.files = ["test.txt", "test.cpp", "another_test.cpp"]
        reporter = PhabricatorReporter(
            {"analyzers": ["coverage", "clang-tidy"]}, api=api
        )

    issue_clang_tidy = ClangTidyIssue(
        mock_task(ClangTidyTask, "source-test-clang-tidy"),
        revision,
        "another_test.cpp",
        "42",
        "51",
        "modernize-use-nullptr",
        "dummy message",
    )
    assert issue_clang_tidy.is_publishable()

    issue_coverage = CoverageIssue(
        mock_task(ZeroCoverageTask, "coverage"),
        "path/to/test.cpp",
        0,
        "This file is uncovered",
        revision,
    )
    assert issue_coverage.is_publishable()

    issues, patches = reporter.publish([issue_clang_tidy, issue_coverage], revision, [])
    assert len(issues) == 2
    assert len(patches) == 0

    # Check the lint results
    assert phab.build_messages["PHID-HMBT-test"] == [
        {
            "buildTargetPHID": "PHID-HMBT-test",
            "lint": [
                {
                    "char": 51,
                    "code": "modernize-use-nullptr",
                    "description": "WARNING: dummy message",
                    "line": 42,
                    "name": "clang-tidy",
                    "path": "another_test.cpp",
                    "severity": "warning",
                },
                {
                    "code": "no-coverage",
                    "description": "WARNING: This file is uncovered",
                    "line": 1,
                    "name": "code coverage analysis",
                    "path": "path/to/test.cpp",
                    "severity": "warning",
                },
            ],
            "type": "work",
            "unit": [],
        }
    ]

    # Check the callback has been used to post unique comment
    assert phab.comments[51] == [VALID_CLANG_TIDY_COVERAGE_MESSAGE]
def test_phabricator_harbormaster(mock_phabricator, mock_try_task):
    """
    Test Phabricator reporter publication on a mock clang-tidy issue
    using harbormaster
    """
    from code_review_bot.report.phabricator import PhabricatorReporter
    from code_review_bot.revisions import Revision
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    def _check_message(request):
        # Check the Phabricator main comment is well formed
        payload = urllib.parse.parse_qs(request.body)
        assert payload["output"] == ["json"]
        assert len(payload["params"]) == 1
        details = json.loads(payload["params"][0])
        assert details == {
            "buildTargetPHID":
            "PHID-HMBD-deadbeef12456",
            "lint": [{
                "char": 51,
                "code": "clang-tidy.modernize-use-nullptr",
                "name": "Clang-Tidy - modernize-use-nullptr",
                "line": 42,
                "path": "test.cpp",
                "severity": "warning",
                "description": "dummy message",
            }],
            "unit": [],
            "type":
            "work",
            "__conduit__": {
                "token": "deadbeef"
            },
        }

        # Outputs dummy empty response
        resp = {"error_code": None, "result": None}
        return (
            201,
            {
                "Content-Type": "application/json",
                "unittest": "clang-tidy"
            },
            json.dumps(resp),
        )

    responses.add_callback(
        responses.POST,
        "http://phabricator.test/api/harbormaster.sendmessage",
        callback=_check_message,
    )

    with mock_phabricator as api:
        revision = Revision(api, mock_try_task)
        revision.lines = {
            # Add dummy lines diff
            "test.cpp": [41, 42, 43]
        }
        revision.build_target_phid = "PHID-HMBD-deadbeef12456"
        reporter = PhabricatorReporter(
            {
                "analyzers": ["clang-tidy"],
                "mode": "harbormaster"
            }, api=api)

    issue = ClangTidyIssue(
        "mock-clang-tidy",
        revision,
        "test.cpp",
        "42",
        "51",
        "modernize-use-nullptr",
        "dummy message",
        "error",
    )
    assert issue.is_publishable()

    issues, patches = reporter.publish([issue], revision, [])
    assert len(issues) == 1
    assert len(patches) == 0

    # Check the callback has been used
    assert len(responses.calls) > 0
    call = responses.calls[-1]
    assert call.request.url == "http://phabricator.test/api/harbormaster.sendmessage"
    assert call.response.headers.get("unittest") == "clang-tidy"
Example #19
0
def test_phabricator_clang_tidy(mock_phabricator, mock_try_task):
    """
    Test Phabricator reporter publication on a mock clang-tidy issue
    """
    from code_review_bot.report.phabricator import PhabricatorReporter
    from code_review_bot.revisions import Revision
    from code_review_bot.tasks.clang_tidy import ClangTidyIssue

    def _check_comment(request):
        # Check the Phabricator main comment is well formed
        payload = urllib.parse.parse_qs(request.body)
        assert payload["output"] == ["json"]
        assert len(payload["params"]) == 1
        details = json.loads(payload["params"][0])
        assert details == {
            "revision_id": 51,
            "message": VALID_CLANG_TIDY_MESSAGE,
            "attach_inlines": 1,
            "__conduit__": {"token": "deadbeef"},
        }

        # Outputs dummy empty response
        resp = {"error_code": None, "result": None}
        return (
            201,
            {"Content-Type": "application/json", "unittest": "clang-tidy"},
            json.dumps(resp),
        )

    responses.add_callback(
        responses.POST,
        "http://phabricator.test/api/differential.createcomment",
        callback=_check_comment,
    )

    with mock_phabricator as api:
        revision = Revision.from_try(mock_try_task, api)
        revision.lines = {
            # Add dummy lines diff
            "another_test.cpp": [41, 42, 43]
        }
        revision.files = ["another_test.cpp"]
        reporter = PhabricatorReporter({"analyzers": ["clang-tidy"]}, api=api)

    issue = ClangTidyIssue(
        "source-test-clang-tidy",
        revision,
        "another_test.cpp",
        "42",
        "51",
        "modernize-use-nullptr",
        "dummy message",
    )
    assert issue.is_publishable()

    issues, patches = reporter.publish([issue], revision, [])
    assert len(issues) == 1
    assert len(patches) == 0

    # Check the callback has been used
    assert len(responses.calls) > 0
    call = responses.calls[-1]
    assert call.request.url == "http://phabricator.test/api/differential.createcomment"
    assert call.response.headers.get("unittest") == "clang-tidy"