1#!/usr/bin/env python3 2# Copyright 2020 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Tests for nightly_revert_checker.""" 7 8import unittest 9from unittest import mock 10 11from cros_utils import tiny_render 12import get_upstream_patch 13import nightly_revert_checker 14import revert_checker 15 16 17# pylint: disable=protected-access 18 19 20class Test(unittest.TestCase): 21 """Tests for nightly_revert_checker.""" 22 23 def test_email_rendering_works_for_singular_revert(self): 24 def prettify_sha(sha: str) -> tiny_render.Piece: 25 return "pretty_" + sha 26 27 def get_sha_description(sha: str) -> tiny_render.Piece: 28 return "subject_" + sha 29 30 email = nightly_revert_checker._generate_revert_email( 31 repository_name="${repo}", 32 friendly_name="${name}", 33 sha="${sha}", 34 prettify_sha=prettify_sha, 35 get_sha_description=get_sha_description, 36 new_reverts=[ 37 revert_checker.Revert( 38 sha="${revert_sha}", reverted_sha="${reverted_sha}" 39 ) 40 ], 41 ) 42 43 expected_email = nightly_revert_checker._Email( 44 subject="[revert-checker/${repo}] new revert discovered across " 45 "${name}", 46 body=[ 47 "It looks like there may be a new revert across ${name} (", 48 "pretty_${sha}", 49 ").", 50 tiny_render.line_break, 51 tiny_render.line_break, 52 "That is:", 53 tiny_render.UnorderedList( 54 [ 55 [ 56 "pretty_${revert_sha}", 57 " (appears to revert ", 58 "pretty_${reverted_sha}", 59 "): ", 60 "subject_${revert_sha}", 61 ] 62 ] 63 ), 64 tiny_render.line_break, 65 "PTAL and consider reverting them locally.", 66 ], 67 ) 68 69 self.assertEqual(email, expected_email) 70 71 def test_email_rendering_works_for_multiple_reverts(self): 72 def prettify_sha(sha: str) -> tiny_render.Piece: 73 return "pretty_" + sha 74 75 def get_sha_description(sha: str) -> tiny_render.Piece: 76 return "subject_" + sha 77 78 email = nightly_revert_checker._generate_revert_email( 79 repository_name="${repo}", 80 friendly_name="${name}", 81 sha="${sha}", 82 prettify_sha=prettify_sha, 83 get_sha_description=get_sha_description, 84 new_reverts=[ 85 revert_checker.Revert( 86 sha="${revert_sha1}", reverted_sha="${reverted_sha1}" 87 ), 88 revert_checker.Revert( 89 sha="${revert_sha2}", reverted_sha="${reverted_sha2}" 90 ), 91 # Keep this out-of-order to check that we sort based on SHAs 92 revert_checker.Revert( 93 sha="${revert_sha0}", reverted_sha="${reverted_sha0}" 94 ), 95 ], 96 ) 97 98 expected_email = nightly_revert_checker._Email( 99 subject="[revert-checker/${repo}] new reverts discovered across " 100 "${name}", 101 body=[ 102 "It looks like there may be new reverts across ${name} (", 103 "pretty_${sha}", 104 ").", 105 tiny_render.line_break, 106 tiny_render.line_break, 107 "These are:", 108 tiny_render.UnorderedList( 109 [ 110 [ 111 "pretty_${revert_sha0}", 112 " (appears to revert ", 113 "pretty_${reverted_sha0}", 114 "): ", 115 "subject_${revert_sha0}", 116 ], 117 [ 118 "pretty_${revert_sha1}", 119 " (appears to revert ", 120 "pretty_${reverted_sha1}", 121 "): ", 122 "subject_${revert_sha1}", 123 ], 124 [ 125 "pretty_${revert_sha2}", 126 " (appears to revert ", 127 "pretty_${reverted_sha2}", 128 "): ", 129 "subject_${revert_sha2}", 130 ], 131 ] 132 ), 133 tiny_render.line_break, 134 "PTAL and consider reverting them locally.", 135 ], 136 ) 137 138 self.assertEqual(email, expected_email) 139 140 @mock.patch("revert_checker.find_reverts") 141 @mock.patch("get_upstream_patch.get_from_upstream") 142 def test_do_cherrypick_is_called(self, do_cherrypick, find_reverts): 143 find_reverts.return_value = [ 144 revert_checker.Revert("12345abcdef", "fedcba54321") 145 ] 146 nightly_revert_checker.do_cherrypick( 147 chroot_path="/path/to/chroot", 148 llvm_dir="/path/to/llvm", 149 repository="repository_name", 150 interesting_shas=[("12345abcdef", "fedcba54321")], 151 state=nightly_revert_checker.State(), 152 reviewers=["[email protected]"], 153 cc=["[email protected]"], 154 ) 155 156 do_cherrypick.assert_called_once() 157 find_reverts.assert_called_once() 158 159 @mock.patch("revert_checker.find_reverts") 160 @mock.patch("get_upstream_patch.get_from_upstream") 161 def test_do_cherrypick_handles_cherrypick_error( 162 self, do_cherrypick, find_reverts 163 ): 164 find_reverts.return_value = [ 165 revert_checker.Revert("12345abcdef", "fedcba54321") 166 ] 167 do_cherrypick.side_effect = get_upstream_patch.CherrypickError( 168 "Patch at 12345abcdef already exists in PATCHES.json" 169 ) 170 nightly_revert_checker.do_cherrypick( 171 chroot_path="/path/to/chroot", 172 llvm_dir="/path/to/llvm", 173 repository="repository_name", 174 interesting_shas=[("12345abcdef", "fedcba54321")], 175 state=nightly_revert_checker.State(), 176 reviewers=["[email protected]"], 177 cc=["[email protected]"], 178 ) 179 180 do_cherrypick.assert_called_once() 181 find_reverts.assert_called_once() 182 183 def test_sha_prettification_for_email(self): 184 sha = "a" * 40 185 rev = 123456 186 self.assertEqual( 187 nightly_revert_checker.prettify_sha_for_email(sha, rev), 188 tiny_render.Switch( 189 text=f"r{rev} ({sha[:12]})", 190 html=tiny_render.Link( 191 href=f"https://github.com/llvm/llvm-project/commit/{sha}", 192 inner=f"r{rev}", 193 ), 194 ), 195 ) 196 197 @mock.patch("time.time") 198 def test_emailing_about_stale_heads_skips_in_simple_cases(self, time_time): 199 now = 1_000_000_000 200 time_time.return_value = now 201 202 def assert_no_email(state: nightly_revert_checker.State): 203 self.assertFalse( 204 nightly_revert_checker.maybe_email_about_stale_heads( 205 state, 206 repository_name="foo", 207 recipients=nightly_revert_checker._EmailRecipients( 208 well_known=[], direct=[] 209 ), 210 prettify_sha=lambda *args: self.fail( 211 "SHAs shouldn't be prettified" 212 ), 213 is_dry_run=True, 214 ) 215 ) 216 217 assert_no_email(nightly_revert_checker.State()) 218 assert_no_email( 219 nightly_revert_checker.State( 220 heads={ 221 "foo": nightly_revert_checker.HeadInfo( 222 last_sha="", 223 first_seen_timestamp=0, 224 next_notification_timestamp=now + 1, 225 ), 226 "bar": nightly_revert_checker.HeadInfo( 227 last_sha="", 228 first_seen_timestamp=0, 229 next_notification_timestamp=now * 2, 230 ), 231 } 232 ) 233 ) 234 235 def test_state_autoupgrades_from_json_properly(self): 236 state = nightly_revert_checker.State.from_json({"abc123": ["def456"]}) 237 self.assertEqual(state.seen_reverts, {"abc123": ["def456"]}) 238 self.assertEqual(state.heads, {}) 239 240 def test_state_round_trips_through_json(self): 241 state = nightly_revert_checker.State( 242 seen_reverts={"abc123": ["def456"]}, 243 heads={ 244 "head_name": nightly_revert_checker.HeadInfo( 245 last_sha="abc", 246 first_seen_timestamp=123, 247 next_notification_timestamp=456, 248 ), 249 }, 250 ) 251 self.assertEqual( 252 state, nightly_revert_checker.State.from_json(state.to_json()) 253 ) 254 255 @mock.patch("time.time") 256 @mock.patch("nightly_revert_checker._send_revert_email") 257 def test_emailing_about_stale_with_one_report( 258 self, send_revert_email, time_time 259 ): 260 def prettify_sha(sha: str) -> str: 261 return f"pretty({sha})" 262 263 now = 1_000_000_000 264 two_days_ago = now - 2 * nightly_revert_checker.ONE_DAY_SECS 265 time_time.return_value = now 266 recipients = nightly_revert_checker._EmailRecipients( 267 well_known=[], direct=[] 268 ) 269 self.assertTrue( 270 nightly_revert_checker.maybe_email_about_stale_heads( 271 nightly_revert_checker.State( 272 heads={ 273 "foo": nightly_revert_checker.HeadInfo( 274 last_sha="<foo sha>", 275 first_seen_timestamp=two_days_ago, 276 next_notification_timestamp=now - 1, 277 ), 278 "bar": nightly_revert_checker.HeadInfo( 279 last_sha="", 280 first_seen_timestamp=0, 281 next_notification_timestamp=now + 1, 282 ), 283 } 284 ), 285 repository_name="repo", 286 recipients=recipients, 287 prettify_sha=prettify_sha, 288 is_dry_run=False, 289 ) 290 ) 291 send_revert_email.assert_called_once() 292 recipients, email = send_revert_email.call_args[0] 293 294 self.assertEqual( 295 tiny_render.render_text_pieces(email.body), 296 "Hi! This is a friendly notification that the current upstream " 297 "LLVM SHA is being tracked by the LLVM revert checker:\n" 298 " - foo at pretty(<foo sha>), which was last updated ~2 days " 299 "ago.\n" 300 "If that's still correct, great! If it looks wrong, the revert " 301 "checker's SHA autodetection may need an update. Please file a " 302 "bug at go/crostc-bug if an update is needed. Thanks!", 303 ) 304 305 306if __name__ == "__main__": 307 unittest.main() 308