1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.openjdk.skara.bots.mlbridge; 24 25 import org.openjdk.skara.email.EmailAddress; 26 import org.openjdk.skara.host.*; 27 import org.openjdk.skara.host.network.URIBuilder; 28 import org.openjdk.skara.mailinglist.MailingListServerFactory; 29 import org.openjdk.skara.test.*; 30 import org.openjdk.skara.vcs.Repository; 31 32 import org.junit.jupiter.api.*; 33 34 import java.io.IOException; 35 import java.nio.charset.StandardCharsets; 36 import java.nio.file.*; 37 import java.time.Duration; 38 import java.util.*; 39 import java.util.regex.Pattern; 40 import java.util.stream.Collectors; 41 42 import static org.junit.jupiter.api.Assertions.*; 43 44 class MailingListBridgeBotTests { 45 private boolean archiveContains(Path archive, String text) { 46 return archiveContainsCount(archive, text) > 0; 47 } 48 49 private int archiveContainsCount(Path archive, String text) { 50 try { 51 var mbox = Files.find(archive, 50, (path, attrs) -> path.toString().endsWith(".mbox")).findAny(); 52 if (mbox.isEmpty()) { 53 return 0; 54 } 55 var lines = Files.readString(mbox.get(), StandardCharsets.UTF_8); 56 var pattern = Pattern.compile(text); 57 int count = 0; 58 for (var line : lines.split("\\R")) { 59 var matcher = pattern.matcher(line); 60 if (matcher.find()) { 61 count++; 62 } 63 } 64 return count; 65 } catch (IOException e) { 66 return 0; 67 } 68 } 69 70 private boolean webrevContains(Path webrev, String text) { 71 try { 72 var index = Files.find(webrev, 5, (path, attrs) -> path.toString().endsWith("index.html")).findAny(); 73 if (index.isEmpty()) { 74 return false; 75 } 76 var lines = Files.readString(index.get(), StandardCharsets.UTF_8); 77 return lines.contains(text); 78 } catch (IOException e) { 79 return false; 80 } 81 } 82 83 private long countSubstrings(String string, String substring) { 84 return Pattern.compile(substring).matcher(string).results().count(); 85 } 86 87 private String noreplyAddress(HostedRepository repository) { 88 return repository.host().getCurrentUserDetails().id() + "+" + 89 repository.host().getCurrentUserDetails().userName() + 90 "@users.noreply.test"; 91 } 92 93 @Test 94 void simpleArchive(TestInfo testInfo) throws IOException { 95 try (var credentials = new HostCredentials(testInfo); 96 var tempFolder = new TemporaryDirectory(); 97 var archiveFolder = new TemporaryDirectory(); 98 var webrevFolder = new TemporaryDirectory(); 99 var listServer = new TestMailmanServer()) { 100 var author = credentials.getHostedRepository(); 101 var archive = credentials.getHostedRepository(); 102 var ignored = credentials.getHostedRepository(); 103 var listAddress = EmailAddress.parse(listServer.createList("test")); 104 var censusBuilder = credentials.getCensusBuilder() 105 .addAuthor(author.host().getCurrentUserDetails().id()); 106 var from = EmailAddress.from("test", "test@test.mail"); 107 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", listAddress, 108 Set.of(ignored.host().getCurrentUserDetails().userName()), 109 Set.of(), 110 listServer.getArchive(), listServer.getSMTP(), 111 archive, "webrev", Path.of("test"), 112 URIBuilder.base("http://www.test.test/").build(), 113 Set.of("rfr"), Map.of(ignored.host().getCurrentUserDetails().userName(), 114 Pattern.compile("ready"))); 115 116 // Populate the projects repository 117 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType()); 118 var masterHash = localRepo.resolve("master").orElseThrow(); 119 localRepo.push(masterHash, author.getUrl(), "master", true); 120 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 121 122 // Make a change with a corresponding PR 123 var editHash = CheckableRepository.appendAndCommit(localRepo, "A simple change", 124 "Change msg\n\nWith several lines"); 125 localRepo.push(editHash, author.getUrl(), "edit", true); 126 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 127 pr.setBody("This should not be ready"); 128 129 // Run an archive pass 130 TestBotRunner.runPeriodicItems(mlBot); 131 132 // A PR that isn't ready for review should not be archived 133 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 134 assertFalse(archiveContains(archiveFolder.path(), "This is a pull request")); 135 136 // Flag it as ready for review 137 pr.setBody("This should now be ready"); 138 pr.addLabel("rfr"); 139 140 // Run another archive pass 141 TestBotRunner.runPeriodicItems(mlBot); 142 143 // But it should still not be archived 144 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 145 assertFalse(archiveContains(archiveFolder.path(), "This is a pull request")); 146 147 // Now post a general comment - not a ready marker 148 var ignoredPr = ignored.getPullRequest(pr.getId()); 149 ignoredPr.addComment("hello there"); 150 151 // Run another archive pass 152 TestBotRunner.runPeriodicItems(mlBot); 153 154 // It should still not be archived 155 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 156 assertFalse(archiveContains(archiveFolder.path(), "This is a pull request")); 157 158 // Now post a ready comment 159 ignoredPr.addComment("ready"); 160 161 // Run another archive pass 162 TestBotRunner.runPeriodicItems(mlBot); 163 164 // The archive should now contain an entry 165 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 166 assertTrue(archiveContains(archiveFolder.path(), "This is a pull request")); 167 assertTrue(archiveContains(archiveFolder.path(), "This should now be ready")); 168 assertTrue(archiveContains(archiveFolder.path(), "Patch:")); 169 assertTrue(archiveContains(archiveFolder.path(), "Changes:")); 170 assertTrue(archiveContains(archiveFolder.path(), "Webrev:")); 171 assertTrue(archiveContains(archiveFolder.path(), "http://www.test.test/")); 172 assertTrue(archiveContains(archiveFolder.path(), "webrev.00")); 173 assertTrue(archiveContains(archiveFolder.path(), "Fetch:")); 174 assertTrue(archiveContains(archiveFolder.path(), "^ - " + editHash.abbreviate() + ": Change msg")); 175 assertFalse(archiveContains(archiveFolder.path(), "With several lines")); 176 177 // The mailing list as well 178 listServer.processIncoming(); 179 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 180 var mailmanList = mailmanServer.getList(listAddress.address()); 181 var conversations = mailmanList.conversations(Duration.ofDays(1)); 182 assertEquals(1, conversations.size()); 183 var mail = conversations.get(0).first(); 184 assertEquals("RFR: This is a pull request", mail.subject()); 185 assertEquals(pr.getAuthor().fullName(), mail.author().fullName().orElseThrow()); 186 assertEquals(noreplyAddress(archive), mail.author().address()); 187 assertEquals(from, mail.sender()); 188 189 // And there should be a webrev 190 Repository.materialize(webrevFolder.path(), archive.getUrl(), "webrev"); 191 assertTrue(webrevContains(webrevFolder.path(), "1 lines changed")); 192 var comments = pr.getComments(); 193 var webrevComments = comments.stream() 194 .filter(comment -> comment.author().equals(author.host().getCurrentUserDetails())) 195 .filter(comment -> comment.body().contains("webrev")) 196 .filter(comment -> comment.body().contains(editHash.hex())) 197 .collect(Collectors.toList()); 198 assertEquals(1, webrevComments.size()); 199 200 // Add a comment 201 pr.addComment("This is a comment :smile:"); 202 203 // Add a comment from an ignored user as well 204 ignoredPr.addComment("Don't mind me"); 205 206 // Run another archive pass 207 TestBotRunner.runPeriodicItems(mlBot); 208 209 // The archive should now contain the comment, but not the ignored one 210 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 211 assertTrue(archiveContains(archiveFolder.path(), "This is a comment")); 212 assertTrue(archiveContains(archiveFolder.path(), "> This should now be ready")); 213 assertFalse(archiveContains(archiveFolder.path(), "Don't mind me")); 214 215 listServer.processIncoming(); 216 conversations = mailmanList.conversations(Duration.ofDays(1)); 217 assertEquals(1, conversations.size()); 218 assertEquals(2, conversations.get(0).allMessages().size()); 219 220 // Remove the rfr flag and post another comment 221 pr.addLabel("rfr"); 222 pr.addComment("This is another comment"); 223 224 // Run another archive pass 225 TestBotRunner.runPeriodicItems(mlBot); 226 227 // The archive should contain the additional comment 228 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 229 assertTrue(archiveContains(archiveFolder.path(), "This is another comment")); 230 assertTrue(archiveContains(archiveFolder.path(), ">> This should now be ready")); 231 232 listServer.processIncoming(); 233 conversations = mailmanList.conversations(Duration.ofDays(1)); 234 assertEquals(1, conversations.size()); 235 assertEquals(3, conversations.get(0).allMessages().size()); 236 for (var newMail : conversations.get(0).allMessages()) { 237 assertEquals(noreplyAddress(archive), newMail.author().address()); 238 assertEquals(from, newMail.sender()); 239 } 240 assertTrue(conversations.get(0).allMessages().get(2).body().contains("This is a comment 😄")); 241 } 242 } 243 244 @Test 245 void reviewComment(TestInfo testInfo) throws IOException { 246 try (var credentials = new HostCredentials(testInfo); 247 var tempFolder = new TemporaryDirectory(); 248 var archiveFolder = new TemporaryDirectory(); 249 var listServer = new TestMailmanServer()) { 250 var author = credentials.getHostedRepository(); 251 var archive = credentials.getHostedRepository(); 252 var ignored = credentials.getHostedRepository(); 253 var listAddress = EmailAddress.parse(listServer.createList("test")); 254 var censusBuilder = credentials.getCensusBuilder() 255 .addAuthor(author.host().getCurrentUserDetails().id()); 256 var from = EmailAddress.from("test", "test@test.mail"); 257 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", listAddress, 258 Set.of(ignored.host().getCurrentUserDetails().userName()), 259 Set.of(), 260 listServer.getArchive(), listServer.getSMTP(), 261 archive, "webrev", Path.of("test"), 262 URIBuilder.base("http://www.test.test/").build(), 263 Set.of(), Map.of()); 264 265 // Populate the projects repository 266 var reviewFile = Path.of("reviewfile.txt"); 267 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 268 var masterHash = localRepo.resolve("master").orElseThrow(); 269 localRepo.push(masterHash, author.getUrl(), "master", true); 270 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 271 272 // Make a change with a corresponding PR 273 var editHash = CheckableRepository.appendAndCommit(localRepo); 274 localRepo.push(editHash, author.getUrl(), "edit", true); 275 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 276 pr.setBody("This is now ready"); 277 TestBotRunner.runPeriodicItems(mlBot); 278 listServer.processIncoming(); 279 280 // And make a file specific comment 281 var currentMaster = localRepo.resolve("master").orElseThrow(); 282 var comment = pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review comment"); 283 284 // Add one from an ignored user as well 285 var ignoredPr = ignored.getPullRequest(pr.getId()); 286 ignoredPr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Don't mind me"); 287 288 // Process comments 289 TestBotRunner.runPeriodicItems(mlBot); 290 listServer.processIncoming(); 291 292 // The archive should now contain an entry 293 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 294 assertTrue(archiveContains(archiveFolder.path(), "This is a pull request")); 295 assertTrue(archiveContains(archiveFolder.path(), "This is now ready")); 296 assertTrue(archiveContains(archiveFolder.path(), "Review comment")); 297 assertTrue(archiveContains(archiveFolder.path(), "> This is now ready")); 298 assertTrue(archiveContains(archiveFolder.path(), reviewFile.toString())); 299 assertFalse(archiveContains(archiveFolder.path(), "Don't mind me")); 300 301 // The mailing list as well 302 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 303 var mailmanList = mailmanServer.getList(listAddress.address()); 304 var conversations = mailmanList.conversations(Duration.ofDays(1)); 305 assertEquals(1, conversations.size()); 306 var mail = conversations.get(0).first(); 307 assertEquals("RFR: This is a pull request", mail.subject()); 308 309 // Comment on the comment 310 pr.addReviewCommentReply(comment, "This is a review reply"); 311 TestBotRunner.runPeriodicItems(mlBot); 312 listServer.processIncoming(); 313 314 // The archive should contain the additional comment (but no quoted footers) 315 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 316 assertTrue(archiveContains(archiveFolder.path(), "This is a review reply")); 317 assertTrue(archiveContains(archiveFolder.path(), ">> This is now ready")); 318 assertFalse(archiveContains(archiveFolder.path(), "^> PR:")); 319 320 // As well as the mailing list 321 conversations = mailmanList.conversations(Duration.ofDays(1)); 322 assertEquals(1, conversations.size()); 323 assertEquals(3, conversations.get(0).allMessages().size()); 324 for (var newMail : conversations.get(0).allMessages()) { 325 assertEquals(noreplyAddress(archive), newMail.author().address()); 326 assertEquals(from, newMail.sender()); 327 } 328 } 329 } 330 331 @Test 332 void combineComments(TestInfo testInfo) throws IOException { 333 try (var credentials = new HostCredentials(testInfo); 334 var tempFolder = new TemporaryDirectory(); 335 var archiveFolder = new TemporaryDirectory(); 336 var listServer = new TestMailmanServer()) { 337 var author = credentials.getHostedRepository(); 338 var archive = credentials.getHostedRepository(); 339 var listAddress = EmailAddress.parse(listServer.createList("test")); 340 var censusBuilder = credentials.getCensusBuilder() 341 .addAuthor(author.host().getCurrentUserDetails().id()); 342 var from = EmailAddress.from("test", "test@test.mail"); 343 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 344 listAddress, Set.of(), Set.of(), 345 listServer.getArchive(), 346 listServer.getSMTP(), 347 archive, "webrev", Path.of("test"), 348 URIBuilder.base("http://www.test.test/").build(), 349 Set.of(), Map.of()); 350 351 // Populate the projects repository 352 var reviewFile = Path.of("reviewfile.txt"); 353 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 354 var masterHash = localRepo.resolve("master").orElseThrow(); 355 localRepo.push(masterHash, author.getUrl(), "master", true); 356 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 357 358 // Make a change with a corresponding PR 359 var editHash = CheckableRepository.appendAndCommit(localRepo); 360 localRepo.push(editHash, author.getUrl(), "edit", true); 361 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 362 pr.setBody("This is now ready"); 363 pr.addComment("Avoid combining"); 364 365 TestBotRunner.runPeriodicItems(mlBot); 366 listServer.processIncoming(); 367 listServer.processIncoming(); 368 369 // Make two file specific comments 370 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review comment"); 371 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Another review comment"); 372 TestBotRunner.runPeriodicItems(mlBot); 373 listServer.processIncoming(); 374 375 // The archive should contain a combined entry 376 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 377 assertEquals(2, archiveContainsCount(archiveFolder.path(), "^On.*wrote:")); 378 379 // As well as the mailing list 380 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 381 var mailmanList = mailmanServer.getList(listAddress.address()); 382 var conversations = mailmanList.conversations(Duration.ofDays(1)); 383 assertEquals(1, conversations.size()); 384 var mail = conversations.get(0).first(); 385 assertEquals("RFR: This is a pull request", mail.subject()); 386 assertEquals(3, conversations.get(0).allMessages().size()); 387 388 var commentReply = conversations.get(0).replies(mail).get(0); 389 assertEquals(2, commentReply.body().split("^On.*wrote:").length); 390 assertTrue(commentReply.body().contains("Avoid combining\n\n"), commentReply.body()); 391 392 var reviewReply = conversations.get(0).replies(mail).get(1); 393 assertEquals(2, reviewReply.body().split("^On.*wrote:").length); 394 assertEquals(2, reviewReply.body().split("> This is now ready").length, reviewReply.body()); 395 assertEquals("Re: RFR: This is a pull request", reviewReply.subject()); 396 assertTrue(reviewReply.body().contains("Review comment\n\n"), reviewReply.body()); 397 assertTrue(reviewReply.body().contains("Another review comment"), reviewReply.body()); 398 } 399 } 400 401 @Test 402 void commentThreading(TestInfo testInfo) throws IOException { 403 try (var credentials = new HostCredentials(testInfo); 404 var tempFolder = new TemporaryDirectory(); 405 var archiveFolder = new TemporaryDirectory(); 406 var listServer = new TestMailmanServer()) { 407 var author = credentials.getHostedRepository(); 408 var reviewer = credentials.getHostedRepository(); 409 var archive = credentials.getHostedRepository(); 410 var listAddress = EmailAddress.parse(listServer.createList("test")); 411 var censusBuilder = credentials.getCensusBuilder() 412 .addReviewer(reviewer.host().getCurrentUserDetails().id()) 413 .addAuthor(author.host().getCurrentUserDetails().id()); 414 var from = EmailAddress.from("test", "test@test.mail"); 415 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 416 listAddress, Set.of(), Set.of(), 417 listServer.getArchive(), 418 listServer.getSMTP(), 419 archive, "webrev", Path.of("test"), 420 URIBuilder.base("http://www.test.test/").build(), 421 Set.of(), Map.of()); 422 423 // Populate the projects repository 424 var reviewFile = Path.of("reviewfile.txt"); 425 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 426 var masterHash = localRepo.resolve("master").orElseThrow(); 427 localRepo.push(masterHash, author.getUrl(), "master", true); 428 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 429 430 // Make a change with a corresponding PR 431 var editHash = CheckableRepository.appendAndCommit(localRepo); 432 localRepo.push(editHash, author.getUrl(), "edit", true); 433 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 434 pr.setBody("This is now ready"); 435 TestBotRunner.runPeriodicItems(mlBot); 436 listServer.processIncoming(); 437 438 // Make a file specific comment 439 var reviewPr = reviewer.getPullRequest(pr.getId()); 440 var comment1 = reviewPr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review comment"); 441 pr.addReviewCommentReply(comment1, "I agree"); 442 reviewPr.addReviewCommentReply(comment1, "Great"); 443 TestBotRunner.runPeriodicItems(mlBot); 444 listServer.processIncoming(); 445 listServer.processIncoming(); 446 listServer.processIncoming(); 447 448 // And a second one by ourselves 449 var comment2 = pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Another review comment"); 450 reviewPr.addReviewCommentReply(comment2, "Sounds good"); 451 pr.addReviewCommentReply(comment2, "Thanks"); 452 TestBotRunner.runPeriodicItems(mlBot); 453 listServer.processIncoming(); 454 listServer.processIncoming(); 455 listServer.processIncoming(); 456 457 // Finally some approvals 458 pr.addReview(Review.Verdict.APPROVED, "Nice"); 459 reviewPr.addReview(Review.Verdict.APPROVED, "Looks fine"); 460 TestBotRunner.runPeriodicItems(mlBot); 461 listServer.processIncoming(); 462 listServer.processIncoming(); 463 464 // Sanity check the archive 465 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 466 assertEquals(8, archiveContainsCount(archiveFolder.path(), "^On.*wrote:")); 467 468 // Check the mailing list 469 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 470 var mailmanList = mailmanServer.getList(listAddress.address()); 471 var conversations = mailmanList.conversations(Duration.ofDays(1)); 472 assertEquals(1, conversations.size()); 473 var mail = conversations.get(0).first(); 474 assertEquals("RFR: This is a pull request", mail.subject()); 475 assertEquals(9, conversations.get(0).allMessages().size()); 476 477 // There should be four separate threads 478 var thread1 = conversations.get(0).replies(mail).get(0); 479 assertEquals(2, thread1.body().split("^On.*wrote:").length); 480 assertEquals(2, thread1.body().split("> This is now ready").length, thread1.body()); 481 assertEquals("Re: RFR: This is a pull request", thread1.subject()); 482 assertTrue(thread1.body().contains("Review comment\n\n"), thread1.body()); 483 assertFalse(thread1.body().contains("Another review comment"), thread1.body()); 484 var thread1reply1 = conversations.get(0).replies(thread1).get(0); 485 assertTrue(thread1reply1.body().contains("I agree")); 486 assertEquals(noreplyAddress(archive), thread1reply1.author().address()); 487 assertEquals(archive.host().getCurrentUserDetails().fullName(), thread1reply1.author().fullName().orElseThrow()); 488 var thread1reply2 = conversations.get(0).replies(thread1reply1).get(0); 489 assertTrue(thread1reply2.body().contains("Great")); 490 assertEquals("integrationreviewer1@openjdk.java.net", thread1reply2.author().address()); 491 assertEquals("Generated Reviewer 1", thread1reply2.author().fullName().orElseThrow()); 492 493 var thread2 = conversations.get(0).replies(mail).get(1); 494 assertEquals(2, thread2.body().split("^On.*wrote:").length); 495 assertEquals(2, thread2.body().split("> This is now ready").length, thread2.body()); 496 assertEquals("Re: RFR: This is a pull request", thread2.subject()); 497 assertFalse(thread2.body().contains("Review comment\n\n"), thread2.body()); 498 assertTrue(thread2.body().contains("Another review comment"), thread2.body()); 499 var thread2reply1 = conversations.get(0).replies(thread2).get(0); 500 assertTrue(thread2reply1.body().contains("Sounds good")); 501 var thread2reply2 = conversations.get(0).replies(thread2reply1).get(0); 502 assertTrue(thread2reply2.body().contains("Thanks")); 503 504 var thread3 = conversations.get(0).replies(mail).get(2); 505 assertEquals("Re: RFR: This is a pull request", thread3.subject()); 506 var thread4 = conversations.get(0).replies(mail).get(3); 507 assertEquals("Re: [Approved] RFR: This is a pull request", thread4.subject()); 508 } 509 } 510 511 @Test 512 void reviewContext(TestInfo testInfo) throws IOException { 513 try (var credentials = new HostCredentials(testInfo); 514 var tempFolder = new TemporaryDirectory(); 515 var archiveFolder = new TemporaryDirectory(); 516 var listServer = new TestMailmanServer()) { 517 var author = credentials.getHostedRepository(); 518 var archive = credentials.getHostedRepository(); 519 var listAddress = EmailAddress.parse(listServer.createList("test")); 520 var censusBuilder = credentials.getCensusBuilder() 521 .addAuthor(author.host().getCurrentUserDetails().id()); 522 var from = EmailAddress.from("test", "test@test.mail"); 523 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 524 listAddress, Set.of(), Set.of(), 525 listServer.getArchive(), 526 listServer.getSMTP(), 527 archive, "webrev", Path.of("test"), 528 URIBuilder.base("http://www.test.test/").build(), 529 Set.of(), Map.of()); 530 531 // Populate the projects repository 532 var reviewFile = Path.of("reviewfile.txt"); 533 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 534 var masterHash = localRepo.resolve("master").orElseThrow(); 535 localRepo.push(masterHash, author.getUrl(), "master", true); 536 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 537 538 // Make a change with a corresponding PR 539 var editHash = CheckableRepository.appendAndCommit(localRepo, "Line 1\nLine 2\nLine 3\nLine 4"); 540 localRepo.push(editHash, author.getUrl(), "edit", true); 541 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 542 pr.setBody("This is now ready"); 543 TestBotRunner.runPeriodicItems(mlBot); 544 listServer.processIncoming(); 545 546 // Make a file specific comment 547 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review comment"); 548 549 TestBotRunner.runPeriodicItems(mlBot); 550 listServer.processIncoming(); 551 552 // The archive should only contain context around line 2 553 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 554 assertTrue(archiveContains(archiveFolder.path(), "^> 2: Line 1$")); 555 assertTrue(archiveContains(archiveFolder.path(), "^> 3: Line 2$")); 556 assertFalse(archiveContains(archiveFolder.path(), "^> 4: Line 3$")); 557 } 558 } 559 560 @Test 561 void multipleReviewContexts(TestInfo testInfo) throws IOException { 562 try (var credentials = new HostCredentials(testInfo); 563 var tempFolder = new TemporaryDirectory(); 564 var archiveFolder = new TemporaryDirectory(); 565 var listServer = new TestMailmanServer()) { 566 var author = credentials.getHostedRepository(); 567 var archive = credentials.getHostedRepository(); 568 var listAddress = EmailAddress.parse(listServer.createList("test")); 569 var censusBuilder = credentials.getCensusBuilder() 570 .addAuthor(author.host().getCurrentUserDetails().id()); 571 var from = EmailAddress.from("test", "test@test.mail"); 572 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 573 listAddress, Set.of(), Set.of(), 574 listServer.getArchive(), 575 listServer.getSMTP(), 576 archive, "webrev", Path.of("test"), 577 URIBuilder.base("http://www.test.test/").build(), 578 Set.of(), Map.of()); 579 580 // Populate the projects repository 581 var reviewFile = Path.of("reviewfile.txt"); 582 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 583 var masterHash = localRepo.resolve("master").orElseThrow(); 584 localRepo.push(masterHash, author.getUrl(), "master", true); 585 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 586 var initialHash = CheckableRepository.appendAndCommit(localRepo, 587 "Line 0.1\nLine 0.2\nLine 0.3\nLine 0.4\n" + 588 "Line 1\nLine 2\nLine 3\nLine 4\n" + 589 "Line 5\nLine 6\nLine 7\nLine 8\n" + 590 "Line 8.1\nLine 8.2\nLine 8.3\nLine 8.4\n" + 591 "Line 9\nLine 10\nLine 11\nLine 12\n" + 592 "Line 13\nLine 14\nLine 15\nLine 16\n"); 593 localRepo.push(initialHash, author.getUrl(), "master"); 594 595 // Make a change with a corresponding PR 596 var current = Files.readString(localRepo.root().resolve(reviewFile), StandardCharsets.UTF_8); 597 var updated = current.replaceAll("Line 2", "Line 2 edit\nLine 2.5"); 598 updated = updated.replaceAll("Line 13", "Line 12.5\nLine 13 edit"); 599 Files.writeString(localRepo.root().resolve(reviewFile), updated, StandardCharsets.UTF_8); 600 var editHash = CheckableRepository.appendAndCommit(localRepo); 601 localRepo.push(editHash, author.getUrl(), "edit", true); 602 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 603 pr.setBody("This is now ready"); 604 TestBotRunner.runPeriodicItems(mlBot); 605 listServer.processIncoming(); 606 607 // Make file specific comments 608 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 7, "Review comment"); 609 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 24, "Another review comment"); 610 611 TestBotRunner.runPeriodicItems(mlBot); 612 listServer.processIncoming(); 613 614 // The archive should only contain context around line 2 and 20 615 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 616 assertTrue(archiveContains(archiveFolder.path(), "reviewfile.txt line 7")); 617 assertTrue(archiveContains(archiveFolder.path(), "^> 6: Line 1$")); 618 assertTrue(archiveContains(archiveFolder.path(), "^> 7: Line 2 edit$")); 619 assertFalse(archiveContains(archiveFolder.path(), "Line 3")); 620 621 assertTrue(archiveContains(archiveFolder.path(), "reviewfile.txt line 24")); 622 assertTrue(archiveContains(archiveFolder.path(), "^> 23: Line 12.5$")); 623 assertTrue(archiveContains(archiveFolder.path(), "^> 24: Line 13 edit$")); 624 assertFalse(archiveContains(archiveFolder.path(), "^Line 15")); 625 } 626 } 627 628 @Test 629 void filterComments(TestInfo testInfo) throws IOException { 630 try (var credentials = new HostCredentials(testInfo); 631 var tempFolder = new TemporaryDirectory(); 632 var archiveFolder = new TemporaryDirectory(); 633 var listServer = new TestMailmanServer()) { 634 var author = credentials.getHostedRepository(); 635 var archive = credentials.getHostedRepository(); 636 var listAddress = EmailAddress.parse(listServer.createList("test")); 637 var censusBuilder = credentials.getCensusBuilder() 638 .addAuthor(author.host().getCurrentUserDetails().id()); 639 var from = EmailAddress.from("test", "test@test.mail"); 640 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 641 listAddress, Set.of(), Set.of(), 642 listServer.getArchive(), listServer.getSMTP(), 643 archive, "webrev", Path.of("test"), 644 URIBuilder.base("http://www.test.test/").build(), 645 Set.of(), Map.of()); 646 647 // Populate the projects repository 648 var reviewFile = Path.of("reviewfile.txt"); 649 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 650 var masterHash = localRepo.resolve("master").orElseThrow(); 651 localRepo.push(masterHash, author.getUrl(), "master", true); 652 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 653 654 // Make a change with a corresponding PR 655 var editHash = CheckableRepository.appendAndCommit(localRepo); 656 localRepo.push(editHash, author.getUrl(), "edit", true); 657 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 658 pr.setBody("This is now ready\n<!-- this is a comment -->\nAnd this is not\n" + 659 "<!-- Anything below this marker will be hidden -->\nStatus stuff"); 660 661 // Make a bunch of comments 662 pr.addComment("Plain comment\n<!-- this is a comment -->"); 663 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review comment <!-- this is a comment -->\n"); 664 pr.addComment("/integrate stuff"); 665 TestBotRunner.runPeriodicItems(mlBot); 666 667 // Run an archive pass 668 TestBotRunner.runPeriodicItems(mlBot); 669 670 // The archive should not contain the comment 671 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 672 assertTrue(archiveContains(archiveFolder.path(), "This is now ready")); 673 assertFalse(archiveContains(archiveFolder.path(), "this is a comment")); 674 assertFalse(archiveContains(archiveFolder.path(), "Status stuff")); 675 assertTrue(archiveContains(archiveFolder.path(), "And this is not")); 676 assertFalse(archiveContains(archiveFolder.path(), "<!--")); 677 assertFalse(archiveContains(archiveFolder.path(), "-->")); 678 assertTrue(archiveContains(archiveFolder.path(), "Plain comment")); 679 assertTrue(archiveContains(archiveFolder.path(), "Review comment")); 680 assertFalse(archiveContains(archiveFolder.path(), "/integrate")); 681 } 682 } 683 684 @Test 685 void incrementalChanges(TestInfo testInfo) throws IOException { 686 try (var credentials = new HostCredentials(testInfo); 687 var tempFolder = new TemporaryDirectory(); 688 var archiveFolder = new TemporaryDirectory(); 689 var listServer = new TestMailmanServer()) { 690 var author = credentials.getHostedRepository(); 691 var archive = credentials.getHostedRepository(); 692 var commenter = credentials.getHostedRepository(); 693 var listAddress = EmailAddress.parse(listServer.createList("test")); 694 var censusBuilder = credentials.getCensusBuilder() 695 .addAuthor(author.host().getCurrentUserDetails().id()); 696 var from = EmailAddress.from("test", "test@test.mail"); 697 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 698 listAddress, Set.of(), Set.of(), 699 listServer.getArchive(), listServer.getSMTP(), 700 archive, "webrev", Path.of("test"), 701 URIBuilder.base("http://www.test.test/").build(), 702 Set.of(), Map.of()); 703 704 // Populate the projects repository 705 var reviewFile = Path.of("reviewfile.txt"); 706 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 707 var masterHash = localRepo.resolve("master").orElseThrow(); 708 localRepo.push(masterHash, author.getUrl(), "master", true); 709 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 710 711 // Make a change with a corresponding PR 712 var editHash = CheckableRepository.appendAndCommit(localRepo); 713 localRepo.push(editHash, author.getUrl(), "edit", true); 714 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 715 pr.setBody("This is now ready"); 716 717 // Run an archive pass 718 TestBotRunner.runPeriodicItems(mlBot); 719 listServer.processIncoming(); 720 721 var nextHash = CheckableRepository.appendAndCommit(localRepo, "Yet one more line", "Fixing"); 722 localRepo.push(nextHash, author.getUrl(), "edit"); 723 724 // Make sure that the push registered 725 var lastHeadHash = pr.getHeadHash(); 726 var refreshCount = 0; 727 do { 728 pr = author.getPullRequest(pr.getId()); 729 if (refreshCount++ > 100) { 730 fail("The PR did not update after the new push"); 731 } 732 } while (pr.getHeadHash().equals(lastHeadHash)); 733 734 // Run another archive pass 735 TestBotRunner.runPeriodicItems(mlBot); 736 TestBotRunner.runPeriodicItems(mlBot); 737 TestBotRunner.runPeriodicItems(mlBot); 738 listServer.processIncoming(); 739 740 // The archive should reference the updated push 741 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 742 assertTrue(archiveContains(archiveFolder.path(), "additional changes")); 743 assertTrue(archiveContains(archiveFolder.path(), "full.*/" + pr.getId() + "/webrev.01")); 744 assertTrue(archiveContains(archiveFolder.path(), "inc.*/" + pr.getId() + "/webrev.00-01")); 745 assertTrue(archiveContains(archiveFolder.path(), "Patch")); 746 assertTrue(archiveContains(archiveFolder.path(), "Fetch")); 747 assertTrue(archiveContains(archiveFolder.path(), "Fixing")); 748 749 // The webrev comment should be updated 750 var comments = pr.getComments(); 751 var webrevComments = comments.stream() 752 .filter(comment -> comment.author().equals(author.host().getCurrentUserDetails())) 753 .filter(comment -> comment.body().contains("webrev")) 754 .filter(comment -> comment.body().contains(nextHash.hex())) 755 .filter(comment -> comment.body().contains(editHash.hex())) 756 .collect(Collectors.toList()); 757 assertEquals(1, webrevComments.size()); 758 759 // Check that sender address is set properly 760 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 761 var mailmanList = mailmanServer.getList(listAddress.address()); 762 var conversations = mailmanList.conversations(Duration.ofDays(1)); 763 assertEquals(1, conversations.size()); 764 for (var newMail : conversations.get(0).allMessages()) { 765 assertEquals(noreplyAddress(archive), newMail.author().address()); 766 assertEquals(from, newMail.sender()); 767 } 768 769 // Add a comment 770 var commenterPr = commenter.getPullRequest(pr.getId()); 771 commenterPr.addReviewComment(masterHash, nextHash, reviewFile.toString(), 2, "Review comment"); 772 TestBotRunner.runPeriodicItems(mlBot); 773 listServer.processIncoming(); 774 775 // Ensure that additional updates are only reported once 776 for (int i = 0; i < 3; ++i) { 777 var anotherHash = CheckableRepository.appendAndCommit(localRepo, "Another line", "Fixing"); 778 localRepo.push(anotherHash, author.getUrl(), "edit"); 779 780 // Make sure that the push registered 781 lastHeadHash = pr.getHeadHash(); 782 refreshCount = 0; 783 do { 784 pr = author.getPullRequest(pr.getId()); 785 if (refreshCount++ > 100) { 786 fail("The PR did not update after the new push"); 787 } 788 } while (pr.getHeadHash().equals(lastHeadHash)); 789 790 TestBotRunner.runPeriodicItems(mlBot); 791 TestBotRunner.runPeriodicItems(mlBot); 792 listServer.processIncoming(); 793 } 794 var updatedConversations = mailmanList.conversations(Duration.ofDays(1)); 795 assertEquals(1, updatedConversations.size()); 796 var conversation = updatedConversations.get(0); 797 assertEquals(6, conversation.allMessages().size()); 798 assertEquals("Re: [Rev 01] RFR: This is a pull request", conversation.allMessages().get(1).subject()); 799 assertEquals("Re: [Rev 01] RFR: This is a pull request", conversation.allMessages().get(2).subject(), conversation.allMessages().get(2).toString()); 800 assertEquals("Re: [Rev 04] RFR: This is a pull request", conversation.allMessages().get(5).subject()); 801 } 802 } 803 804 @Test 805 void rebased(TestInfo testInfo) throws IOException { 806 try (var credentials = new HostCredentials(testInfo); 807 var tempFolder = new TemporaryDirectory(); 808 var archiveFolder = new TemporaryDirectory(); 809 var listServer = new TestMailmanServer()) { 810 var author = credentials.getHostedRepository(); 811 var archive = credentials.getHostedRepository(); 812 var listAddress = EmailAddress.parse(listServer.createList("test")); 813 var censusBuilder = credentials.getCensusBuilder() 814 .addAuthor(author.host().getCurrentUserDetails().id()); 815 var sender = EmailAddress.from("test", "test@test.mail"); 816 var mlBot = new MailingListBridgeBot(sender, author, archive, censusBuilder.build(), "master", 817 listAddress, Set.of(), Set.of(), 818 listServer.getArchive(), listServer.getSMTP(), 819 archive, "webrev", Path.of("test"), 820 URIBuilder.base("http://www.test.test/").build(), 821 Set.of(), Map.of()); 822 823 // Populate the projects repository 824 var reviewFile = Path.of("reviewfile.txt"); 825 var localRepo = CheckableRepository.init(tempFolder.path().resolve("first"), author.getRepositoryType(), reviewFile); 826 var masterHash = localRepo.resolve("master").orElseThrow(); 827 localRepo.push(masterHash, author.getUrl(), "master", true); 828 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 829 830 // Make a change with a corresponding PR 831 var editHash = CheckableRepository.appendAndCommit(localRepo, "A line", "Original msg"); 832 localRepo.push(editHash, author.getUrl(), "edit", true); 833 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 834 pr.setBody("This is now ready"); 835 836 // Run an archive pass 837 TestBotRunner.runPeriodicItems(mlBot); 838 listServer.processIncoming(); 839 840 var newLocalRepo = Repository.materialize(tempFolder.path().resolve("second"), author.getUrl(), "master"); 841 var newEditHash = CheckableRepository.appendAndCommit(newLocalRepo, "Another line", "Replaced msg"); 842 newLocalRepo.push(newEditHash, author.getUrl(), "edit", true); 843 844 // Make sure that the push registered 845 var lastHeadHash = pr.getHeadHash(); 846 var refreshCount = 0; 847 do { 848 pr = author.getPullRequest(pr.getId()); 849 if (refreshCount++ > 100) { 850 fail("The PR did not update after the new push"); 851 } 852 } while (pr.getHeadHash().equals(lastHeadHash)); 853 854 // Run another archive pass 855 TestBotRunner.runPeriodicItems(mlBot); 856 listServer.processIncoming(); 857 858 // The archive should reference the rebased push 859 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 860 assertTrue(archiveContains(archiveFolder.path(), "complete new set of changes")); 861 assertTrue(archiveContains(archiveFolder.path(), pr.getId() + "/webrev.01")); 862 assertFalse(archiveContains(archiveFolder.path(), "Incremental")); 863 assertTrue(archiveContains(archiveFolder.path(), "Patch")); 864 assertTrue(archiveContains(archiveFolder.path(), "Fetch")); 865 assertTrue(archiveContains(archiveFolder.path(), "Original msg")); 866 assertTrue(archiveContains(archiveFolder.path(), "Replaced msg")); 867 868 // The webrev comment should be updated 869 var comments = pr.getComments(); 870 var webrevComments = comments.stream() 871 .filter(comment -> comment.author().equals(author.host().getCurrentUserDetails())) 872 .filter(comment -> comment.body().contains("webrev")) 873 .filter(comment -> comment.body().contains(newEditHash.hex())) 874 .collect(Collectors.toList()); 875 assertEquals(1, webrevComments.size()); 876 877 // Check that sender address is set properly 878 var mailmanServer = MailingListServerFactory.createMailmanServer(listServer.getArchive(), listServer.getSMTP()); 879 var mailmanList = mailmanServer.getList(listAddress.address()); 880 var conversations = mailmanList.conversations(Duration.ofDays(1)); 881 assertEquals(1, conversations.size()); 882 for (var newMail : conversations.get(0).allMessages()) { 883 assertEquals(noreplyAddress(archive), newMail.author().address()); 884 assertEquals(sender, newMail.sender()); 885 assertFalse(newMail.hasHeader("PR-Head-Hash")); 886 } 887 assertEquals("Re: [Rev 01] RFR: This is a pull request", conversations.get(0).allMessages().get(1).subject()); 888 } 889 } 890 891 @Test 892 void skipAddingExistingWebrev(TestInfo testInfo) throws IOException { 893 try (var credentials = new HostCredentials(testInfo); 894 var tempFolder = new TemporaryDirectory(); 895 var archiveFolder = new TemporaryDirectory(); 896 var webrevFolder = new TemporaryDirectory(); 897 var listServer = new TestMailmanServer()) { 898 var author = credentials.getHostedRepository(); 899 var archive = credentials.getHostedRepository(); 900 var ignored = credentials.getHostedRepository(); 901 var listAddress = EmailAddress.parse(listServer.createList("test")); 902 var censusBuilder = credentials.getCensusBuilder() 903 .addAuthor(author.host().getCurrentUserDetails().id()); 904 var from = EmailAddress.from("test", "test@test.mail"); 905 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 906 listAddress, 907 Set.of(ignored.host().getCurrentUserDetails().userName()), 908 Set.of(), 909 listServer.getArchive(), listServer.getSMTP(), 910 archive, "webrev", Path.of("test"), 911 URIBuilder.base("http://www.test.test/").build(), 912 Set.of(), Map.of()); 913 914 // Populate the projects repository 915 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType()); 916 var masterHash = localRepo.resolve("master").orElseThrow(); 917 localRepo.push(masterHash, author.getUrl(), "master", true); 918 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 919 920 // Make a change with a corresponding PR 921 var editHash = CheckableRepository.appendAndCommit(localRepo, "A simple change", 922 "Change msg\n\nWith several lines"); 923 localRepo.push(editHash, author.getUrl(), "edit", true); 924 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 925 926 // Flag it as ready for review 927 pr.setBody("This should now be ready"); 928 929 // Run an archive pass 930 TestBotRunner.runPeriodicItems(mlBot); 931 932 // The archive should now contain an entry 933 var archiveRepo = Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 934 assertTrue(archiveContains(archiveFolder.path(), editHash.abbreviate())); 935 936 // And there should be a webrev comment 937 var comments = pr.getComments(); 938 var webrevComments = comments.stream() 939 .filter(comment -> comment.author().equals(author.host().getCurrentUserDetails())) 940 .filter(comment -> comment.body().contains("webrev")) 941 .filter(comment -> comment.body().contains(editHash.hex())) 942 .collect(Collectors.toList()); 943 assertEquals(1, webrevComments.size()); 944 assertEquals(1, countSubstrings(webrevComments.get(0).body(), "webrev.00")); 945 946 // Pretend the archive didn't work out 947 archiveRepo.push(masterHash, archive.getUrl(), "master", true); 948 949 // Run another archive pass 950 TestBotRunner.runPeriodicItems(mlBot); 951 952 // The webrev comment should not contain duplicate entries 953 comments = pr.getComments(); 954 webrevComments = comments.stream() 955 .filter(comment -> comment.author().equals(author.host().getCurrentUserDetails())) 956 .filter(comment -> comment.body().contains("webrev")) 957 .filter(comment -> comment.body().contains(editHash.hex())) 958 .collect(Collectors.toList()); 959 assertEquals(1, webrevComments.size()); 960 assertEquals(1, countSubstrings(webrevComments.get(0).body(), "webrev.00")); 961 } 962 } 963 964 @Test 965 void notifyReviewVerdicts(TestInfo testInfo) throws IOException { 966 try (var credentials = new HostCredentials(testInfo); 967 var tempFolder = new TemporaryDirectory(); 968 var archiveFolder = new TemporaryDirectory(); 969 var listServer = new TestMailmanServer()) { 970 var author = credentials.getHostedRepository(); 971 var archive = credentials.getHostedRepository(); 972 var reviewer = credentials.getHostedRepository(); 973 var listAddress = EmailAddress.parse(listServer.createList("test")); 974 var from = EmailAddress.from("test", "test@test.mail"); 975 var censusBuilder = credentials.getCensusBuilder() 976 .addReviewer(reviewer.host().getCurrentUserDetails().id()) 977 .addAuthor(author.host().getCurrentUserDetails().id()); 978 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 979 listAddress, Set.of(), Set.of(), 980 listServer.getArchive(), listServer.getSMTP(), 981 archive, "webrev", Path.of("test"), 982 URIBuilder.base("http://www.test.test/").build(), 983 Set.of(), Map.of()); 984 985 // Populate the projects repository 986 var reviewFile = Path.of("reviewfile.txt"); 987 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 988 var masterHash = localRepo.resolve("master").orElseThrow(); 989 localRepo.push(masterHash, author.getUrl(), "master", true); 990 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 991 992 // Make a change with a corresponding PR 993 var editHash = CheckableRepository.appendAndCommit(localRepo); 994 localRepo.push(editHash, author.getUrl(), "edit", true); 995 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 996 pr.setBody("This is now ready"); 997 998 // Run an archive pass 999 TestBotRunner.runPeriodicItems(mlBot); 1000 1001 // First unapprove it 1002 var reviewedPr = reviewer.getPullRequest(pr.getId()); 1003 reviewedPr.addReview(Review.Verdict.DISAPPROVED, "Reason 1"); 1004 TestBotRunner.runPeriodicItems(mlBot); 1005 TestBotRunner.runPeriodicItems(mlBot); 1006 TestBotRunner.runPeriodicItems(mlBot); 1007 1008 // The archive should contain a note 1009 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 1010 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Disapproved by ")); 1011 assertEquals(1, archiveContainsCount(archiveFolder.path(), " by integrationreviewer1")); 1012 if (author.host().supportsReviewBody()) { 1013 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Reason 1")); 1014 } 1015 1016 // Then approve it 1017 reviewedPr.addReview(Review.Verdict.APPROVED, "Reason 2"); 1018 TestBotRunner.runPeriodicItems(mlBot); 1019 TestBotRunner.runPeriodicItems(mlBot); 1020 TestBotRunner.runPeriodicItems(mlBot); 1021 1022 // The archive should contain another note 1023 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 1024 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Approved by ")); 1025 if (author.host().supportsReviewBody()) { 1026 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Reason 2")); 1027 } 1028 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Re: \\[Approved\\] RFR:")); 1029 1030 // Yet another change 1031 reviewedPr.addReview(Review.Verdict.DISAPPROVED, "Reason 3"); 1032 TestBotRunner.runPeriodicItems(mlBot); 1033 TestBotRunner.runPeriodicItems(mlBot); 1034 TestBotRunner.runPeriodicItems(mlBot); 1035 1036 // The archive should contain another note 1037 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 1038 assertEquals(2, archiveContainsCount(archiveFolder.path(), "Disapproved by ")); 1039 if (author.host().supportsReviewBody()) { 1040 assertEquals(1, archiveContainsCount(archiveFolder.path(), "Reason 3")); 1041 } 1042 } 1043 } 1044 1045 @Test 1046 void ignoreComments(TestInfo testInfo) throws IOException { 1047 try (var credentials = new HostCredentials(testInfo); 1048 var tempFolder = new TemporaryDirectory(); 1049 var archiveFolder = new TemporaryDirectory(); 1050 var listServer = new TestMailmanServer()) { 1051 var author = credentials.getHostedRepository(); 1052 var ignored = credentials.getHostedRepository(); 1053 var archive = credentials.getHostedRepository(); 1054 var listAddress = EmailAddress.parse(listServer.createList("test")); 1055 var censusBuilder = credentials.getCensusBuilder() 1056 .addAuthor(author.host().getCurrentUserDetails().id()); 1057 var from = EmailAddress.from("test", "test@test.mail"); 1058 var mlBot = new MailingListBridgeBot(from, author, archive, censusBuilder.build(), "master", 1059 listAddress, 1060 Set.of(ignored.host().getCurrentUserDetails().userName()), 1061 Set.of(Pattern.compile("ignore this comment", Pattern.MULTILINE | Pattern.DOTALL)), 1062 listServer.getArchive(), listServer.getSMTP(), 1063 archive, "webrev", Path.of("test"), 1064 URIBuilder.base("http://www.test.test/").build(), 1065 Set.of(), Map.of()); 1066 1067 // Populate the projects repository 1068 var reviewFile = Path.of("reviewfile.txt"); 1069 var localRepo = CheckableRepository.init(tempFolder.path(), author.getRepositoryType(), reviewFile); 1070 var masterHash = localRepo.resolve("master").orElseThrow(); 1071 localRepo.push(masterHash, author.getUrl(), "master", true); 1072 localRepo.push(masterHash, archive.getUrl(), "webrev", true); 1073 1074 // Make a change with a corresponding PR 1075 var editHash = CheckableRepository.appendAndCommit(localRepo); 1076 localRepo.push(editHash, author.getUrl(), "edit", true); 1077 var pr = credentials.createPullRequest(archive, "master", "edit", "This is a pull request"); 1078 pr.setBody("This is now ready"); 1079 1080 // Make a bunch of comments 1081 pr.addComment("Plain comment"); 1082 pr.addComment("ignore this comment"); 1083 pr.addComment("I think it is time to\nignore this comment!"); 1084 pr.addReviewComment(masterHash, editHash, reviewFile.toString(), 2, "Review ignore this comment"); 1085 1086 var ignoredPR = ignored.getPullRequest(pr.getId()); 1087 ignoredPR.addComment("Don't mind me"); 1088 1089 TestBotRunner.runPeriodicItems(mlBot); 1090 TestBotRunner.runPeriodicItems(mlBot); 1091 1092 // The archive should not contain the ignored comments 1093 Repository.materialize(archiveFolder.path(), archive.getUrl(), "master"); 1094 assertTrue(archiveContains(archiveFolder.path(), "This is now ready")); 1095 assertFalse(archiveContains(archiveFolder.path(), "ignore this comment")); 1096 assertFalse(archiveContains(archiveFolder.path(), "it is time to")); 1097 assertFalse(archiveContains(archiveFolder.path(), "Don't mind me")); 1098 assertFalse(archiveContains(archiveFolder.path(), "Review ignore")); 1099 } 1100 } 1101 }