|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# This Source Code Form is subject to the terms of the Mozilla Public |
| 4 | +# License, v. 2.0. If a copy of the MPL was not distributed with this |
| 5 | +# file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 6 | + |
| 7 | +import sys |
| 8 | +import unittest |
| 9 | + |
| 10 | +sys.path.append(".") |
| 11 | +sys.path.append("..") |
| 12 | + |
| 13 | +from components.scmprovider import SCMProvider, Commit |
| 14 | + |
| 15 | +GITHUB_REPO = "https://github.com/org/repo" |
| 16 | + |
| 17 | + |
| 18 | +def make_commit(revision, parent_revision, summary="Do a thing", |
| 19 | + description="A longer description of the thing.", |
| 20 | + files_modified=None): |
| 21 | + # Build a fully-populated Commit without touching a real repository. |
| 22 | + c = Commit("%s|2020-01-01 00:00:00 +0000|2020-01-02 00:00:00 +0000" % revision) |
| 23 | + c.summary = summary |
| 24 | + c.author = "Some Developer <dev@example.com>" |
| 25 | + c.description = description |
| 26 | + c.revision_link = "%s/commit/%s" % (GITHUB_REPO, revision) |
| 27 | + c.parent_revision = parent_revision |
| 28 | + c.files_modified = files_modified if files_modified is not None else ["src/thing.c"] |
| 29 | + c.populated = True |
| 30 | + return c |
| 31 | + |
| 32 | + |
| 33 | +class TestBuildBugDescription(unittest.TestCase): |
| 34 | + @classmethod |
| 35 | + def setUpClass(cls): |
| 36 | + # build_bug_description / _commit_block don't use the command or logging |
| 37 | + # providers, so a bare provider is enough. |
| 38 | + cls.scm = SCMProvider({}) |
| 39 | + |
| 40 | + def _commits(self): |
| 41 | + # Ordered oldest -> newest, as build_bug_description expects. |
| 42 | + return [ |
| 43 | + make_commit("aaaaaaa", "p0000000", summary="First commit"), |
| 44 | + make_commit("bbbbbbb", "aaaaaaa", summary="Second commit"), |
| 45 | + make_commit("ccccccc", "bbbbbbb", summary="Third commit"), |
| 46 | + ] |
| 47 | + |
| 48 | + # -- _commit_block --------------------------------------------------------- |
| 49 | + |
| 50 | + def test_commit_block_verbosity_levels(self): |
| 51 | + c = make_commit("abcdef0", "0fedcba", summary="Fix the widget", |
| 52 | + description="Body of the change.", files_modified=["src/w.c"]) |
| 53 | + |
| 54 | + v1 = self.scm._commit_block(c, 1) |
| 55 | + self.assertIn("abcdef0 by Some Developer", v1) |
| 56 | + self.assertIn(c.revision_link, v1) |
| 57 | + self.assertNotIn("Authored:", v1) |
| 58 | + self.assertNotIn("Fix the widget", v1) |
| 59 | + self.assertNotIn("Files Modified", v1) |
| 60 | + |
| 61 | + v2 = self.scm._commit_block(c, 2) |
| 62 | + self.assertIn("Authored:", v2) |
| 63 | + self.assertIn("Committed:", v2) |
| 64 | + self.assertIn("Fix the widget", v2) |
| 65 | + self.assertNotIn("Body of the change.", v2) |
| 66 | + self.assertNotIn("Files Modified", v2) |
| 67 | + |
| 68 | + v3 = self.scm._commit_block(c, 3) |
| 69 | + self.assertIn("Fix the widget", v3) |
| 70 | + self.assertIn("Body of the change.", v3) |
| 71 | + self.assertIn("Files Modified", v3) |
| 72 | + self.assertIn("src/w.c", v3) |
| 73 | + |
| 74 | + def test_commit_block_requires_populated(self): |
| 75 | + c = Commit("deadbee|d|d") # not populated |
| 76 | + with self.assertRaises(Exception): |
| 77 | + self.scm._commit_block(c, 1) |
| 78 | + |
| 79 | + # -- dispatch -------------------------------------------------------------- |
| 80 | + |
| 81 | + def test_dispatch_selects_builder(self): |
| 82 | + commits = self._commits() |
| 83 | + |
| 84 | + # Tiny budget makes the two behaviors clearly distinguishable: the |
| 85 | + # single-comment builder elides, the chained builder splits instead. |
| 86 | + single = self.scm.build_bug_description(commits, 60, GITHUB_REPO, options={}) |
| 87 | + self.assertEqual(len(single), 1) |
| 88 | + self.assertIn("commits elided", single[0]) |
| 89 | + |
| 90 | + chained = self.scm.build_bug_description(commits, 60, GITHUB_REPO, options={"verbose-diff": True}) |
| 91 | + self.assertGreater(len(chained), 1) |
| 92 | + self.assertNotIn("commits elided", "".join(chained)) |
| 93 | + |
| 94 | + def test_options_default_is_single_comment(self): |
| 95 | + commits = self._commits() |
| 96 | + # No options passed at all -> single-comment behavior. |
| 97 | + result = self.scm.build_bug_description(commits, 60, GITHUB_REPO) |
| 98 | + self.assertEqual(len(result), 1) |
| 99 | + self.assertIn("commits elided", result[0]) |
| 100 | + |
| 101 | + # -- single-comment behavior ---------------------------------------------- |
| 102 | + |
| 103 | + def test_single_comment_full_when_it_fits(self): |
| 104 | + commits = self._commits() |
| 105 | + result = self.scm.build_bug_description(commits, 65534, GITHUB_REPO, options={}) |
| 106 | + self.assertEqual(len(result), 1) |
| 107 | + self.assertNotIn("commits elided", result[0]) |
| 108 | + # Full detail present, newest commit first. |
| 109 | + self.assertIn("Third commit", result[0]) |
| 110 | + self.assertIn("Files Modified", result[0]) |
| 111 | + self.assertLess(result[0].index("Third commit"), result[0].index("First commit")) |
| 112 | + |
| 113 | + # -- chained behavior ------------------------------------------------------ |
| 114 | + |
| 115 | + def test_chained_single_chunk_when_it_fits(self): |
| 116 | + commits = self._commits() |
| 117 | + result = self.scm.build_bug_description(commits, 65534, GITHUB_REPO, options={"verbose-diff": True}) |
| 118 | + self.assertEqual(len(result), 1) |
| 119 | + # No continuation header for a single chunk. |
| 120 | + self.assertNotIn("continued in following comments", result[0]) |
| 121 | + |
| 122 | + def test_chained_splits_with_compare_url_including_oldest(self): |
| 123 | + commits = self._commits() |
| 124 | + result = self.scm.build_bug_description(commits, 200, GITHUB_REPO, options={"verbose-diff": True}) |
| 125 | + self.assertGreater(len(result), 1) |
| 126 | + # The compare range starts at the parent of the oldest commit, so the |
| 127 | + # oldest commit itself is included. |
| 128 | + self.assertIn("All 3 commits: %s/compare/p0000000...ccccccc" % GITHUB_REPO, result[0]) |
| 129 | + self.assertIn("continued in following comments", result[0]) |
| 130 | + |
| 131 | + def test_chained_no_compare_url_for_unsupported_host(self): |
| 132 | + commits = self._commits() |
| 133 | + result = self.scm.build_bug_description(commits, 200, "https://example.com/repo", options={"verbose-diff": True}) |
| 134 | + self.assertGreater(len(result), 1) |
| 135 | + self.assertNotIn("/compare/", "".join(result)) |
| 136 | + |
| 137 | + def test_chained_reduces_verbosity_for_oversized_commit(self): |
| 138 | + # A single commit whose full detail exceeds the limit is rendered at a |
| 139 | + # lower verbosity so it fits, rather than blowing past the comment size. |
| 140 | + big = make_commit("abc1234", "par0000", summary="Summary line", |
| 141 | + description="D" * 500, files_modified=["src/one.c", "src/two.c"]) |
| 142 | + result = self.scm.build_bug_description([big], 200, GITHUB_REPO, options={"verbose-diff": True}) |
| 143 | + self.assertEqual(len(result), 1) |
| 144 | + self.assertLessEqual(len(result[0]), 200) # it now fits within the limit |
| 145 | + self.assertIn("abc1234 by", result[0]) # still identifies the commit |
| 146 | + self.assertNotIn("D" * 500, result[0]) # full description dropped |
| 147 | + self.assertNotIn("Files Modified", result[0]) # file list dropped |
| 148 | + |
| 149 | + def test_chained_empty_commit_list(self): |
| 150 | + result = self.scm.build_bug_description([], 65534, GITHUB_REPO, options={"verbose-diff": True}) |
| 151 | + self.assertEqual(result, [""]) |
| 152 | + |
| 153 | + |
| 154 | +if __name__ == "__main__": |
| 155 | + unittest.main(verbosity=2) |
0 commit comments