github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/tree_test.go (about) 1 // Copyright 2023 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "fmt" 8 "reflect" 9 "regexp" 10 "sort" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/google/go-cmp/cmp" 16 "github.com/google/syzkaller/dashboard/dashapi" 17 "github.com/stretchr/testify/assert" 18 db "google.golang.org/appengine/v2/datastore" 19 aemail "google.golang.org/appengine/v2/mail" 20 ) 21 22 func TestTreeOriginDownstream(t *testing.T) { 23 c := NewCtx(t) 24 defer c.Close() 25 26 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 27 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 28 ctx.entries = []treeTestEntry{ 29 { 30 alias: `downstream`, 31 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 32 }, 33 { 34 alias: `lts`, 35 mergeAlias: `downstream`, 36 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 37 }, 38 { 39 alias: `upstream`, 40 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 41 }, 42 } 43 ctx.reportToEmail() 44 ctx.jobTestDays = []int{10} 45 ctx.moveToDay(10) 46 ctx.ensureLabels(`origin:downstream`) 47 // It should habe been enough to run jobs just once. 48 c.expectEQ(ctx.entries[0].jobsDone, 1) 49 c.expectEQ(ctx.entries[1].jobsDone, 1) 50 c.expectEQ(ctx.entries[2].jobsDone, 1) 51 // Test that we can render the bug page. 52 _, err := c.GET(ctx.bugLink()) 53 c.expectEQ(err, nil) 54 // Test that we receive a notification. 55 msg := ctx.emailWithoutURLs() 56 c.expectEQ(msg.Body, `Bug presence analysis results: the bug reproduces only on the downstream tree. 57 58 syzbot has run the reproducer on other relevant kernel trees and got 59 the following results: 60 61 downstream (commit ffffffffffff) on 2000/01/11: 62 crash title 63 Report: %URL% 64 65 lts (commit ffffffffffff) on 2000/01/11: 66 Didn't crash. 67 68 upstream (commit ffffffffffff) on 2000/01/11: 69 Didn't crash. 70 71 More details can be found at: 72 %URL% 73 `) 74 // Test that these results are also in the full bug info. 75 info := ctx.fullBugInfo() 76 c.expectEQ(len(info.TreeJobs), 3) 77 c.expectEQ(info.TreeJobs[0].KernelAlias, `downstream`) 78 c.expectNE(info.TreeJobs[0].CrashTitle, ``) 79 c.expectEQ(info.TreeJobs[1].KernelAlias, `lts`) 80 c.expectEQ(info.TreeJobs[1].CrashTitle, ``) 81 c.expectEQ(info.TreeJobs[2].KernelAlias, `upstream`) 82 c.expectEQ(info.TreeJobs[2].CrashTitle, ``) 83 } 84 85 func TestTreeOriginDownstreamEmail(t *testing.T) { 86 c := NewCtx(t) 87 defer c.Close() 88 89 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 90 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 91 ctx.entries = []treeTestEntry{ 92 { 93 alias: `downstream`, 94 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 95 }, 96 { 97 alias: `lts`, 98 mergeAlias: `downstream`, 99 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 100 }, 101 { 102 alias: `upstream`, 103 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 104 }, 105 } 106 ctx.jobTestDays = []int{10} 107 ctx.moveToDay(10) 108 109 // The report must contain the string. 110 msg := ctx.reportToEmail() 111 assert.Contains(t, msg.Body, `@testapp.appspotmail.com 112 113 Bug presence analysis results: the bug reproduces only on the downstream tree. 114 115 116 report1 117 118 --- 119 This report is generated by a bot. It may contain errors.`) 120 // No notification must be sent. 121 c.client.pollNotifs(0) 122 } 123 124 func TestTreeOriginBetterReport(t *testing.T) { 125 // Ensure that, once a higher priority crash becomes available, we perform origin testing again. 126 c := NewCtx(t) 127 defer c.Close() 128 129 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 130 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 131 ctx.entries = []treeTestEntry{ 132 { 133 alias: `downstream`, 134 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 135 }, 136 { 137 alias: `lts`, 138 mergeAlias: `downstream`, 139 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 140 }, 141 { 142 alias: `upstream`, 143 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 144 }, 145 } 146 ctx.jobTestDays = []int{10, 20, 30} 147 ctx.moveToDay(10) 148 ctx.ensureLabels("origin:downstream") 149 c.expectEQ(ctx.entries[1].jobsDone, 1) 150 c.expectEQ(ctx.entries[2].jobsDone, 1) 151 152 // No retets are needed yet. 153 ctx.moveToDay(20) 154 c.expectEQ(ctx.entries[1].jobsDone, 1) 155 c.expectEQ(ctx.entries[2].jobsDone, 1) 156 157 // Use a "better" manager. 158 ctx.manager = "better-manager" 159 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 160 161 // With that reproducer, lts begins to crash as well. 162 ctx.entries[1].results = []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}} 163 ctx.moveToDay(30) 164 ctx.ensureLabels("origin:lts") 165 c.expectEQ(ctx.entries[1].jobsDone, 2) 166 c.expectEQ(ctx.entries[2].jobsDone, 2) 167 } 168 169 func TestTreeOriginLts(t *testing.T) { 170 c := NewCtx(t) 171 defer c.Close() 172 173 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 174 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 175 ctx.entries = []treeTestEntry{ 176 { 177 alias: `downstream`, 178 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 179 }, 180 { 181 alias: `lts`, 182 mergeAlias: `downstream`, 183 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 184 }, 185 { 186 alias: `upstream`, 187 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 188 }, 189 } 190 ctx.jobTestDays = []int{10} 191 ctx.moveToDay(10) 192 ctx.ensureLabels(`origin:lts`) 193 c.expectEQ(ctx.entries[0].jobsDone, 0) 194 c.expectEQ(ctx.entries[1].jobsDone, 1) 195 c.expectEQ(ctx.entries[2].jobsDone, 1) 196 // Test that we don't receive any notification. 197 ctx.reportToEmail() 198 ctx.ctx.expectNoEmail() 199 } 200 201 // This function is very very big, but the required scenario is unfortunately 202 // also very big, so: 203 // nolint: funlen 204 func TestTreeOriginLtsBisection(t *testing.T) { 205 c := NewCtx(t) 206 defer c.Close() 207 208 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 209 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 210 ctx.entries = []treeTestEntry{ 211 { 212 alias: `downstream`, 213 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 214 }, 215 { 216 alias: `lts`, 217 mergeAlias: `downstream`, 218 results: []treeTestEntryPeriod{ 219 { 220 fromDay: 0, 221 result: treeTestCrash, 222 commit: "badc0ffee", 223 }, 224 }, 225 }, 226 { 227 alias: `upstream`, 228 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 229 }, 230 } 231 ctx.jobTestDays = []int{10} 232 ctx.moveToDay(10) 233 ctx.ensureLabels(`origin:lts`) 234 ctx.reportToEmail() 235 ctx.ctx.advanceTime(time.Hour) 236 237 // Expect a cross tree bisection request. 238 job := ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 239 assert.Equal(t, dashapi.JobBisectFix, job.Type) 240 assert.Equal(t, "https://upstream.repo/repo", job.KernelRepo) 241 assert.Equal(t, "upstream-master", job.KernelBranch) 242 assert.Equal(t, "https://lts.repo/repo", job.MergeBaseRepo) 243 assert.Equal(t, "lts-master", job.MergeBaseBranch) 244 assert.Equal(t, "badc0ffee", job.KernelCommit) 245 ctx.ctx.advanceTime(time.Hour) 246 247 // Make sure we don't create the same job twice. 248 job2 := ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 249 assert.Equal(t, "", job2.ID) 250 ctx.ctx.advanceTime(time.Hour) 251 252 // Let the bisection fail. 253 done := &dashapi.JobDoneReq{ 254 ID: job.ID, 255 Log: []byte("bisect log"), 256 Error: []byte("bisect error"), 257 } 258 c.expectOK(ctx.client.JobDone(done)) 259 ctx.ctx.advanceTime(time.Hour) 260 261 // Ensure there are no new bisection requests. 262 job = ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 263 assert.Equal(t, job.ID, "") 264 265 // Wait for the cooldown and request the job once more. 266 ctx.ctx.advanceTime(15 * 24 * time.Hour) 267 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 268 ctx.ctx.advanceTime(15 * 24 * time.Hour) 269 job = ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 270 assert.Equal(t, job.KernelRepo, "https://upstream.repo/repo") 271 assert.Equal(t, job.KernelCommit, "badc0ffee") 272 273 // This time pretend we have found the commit. 274 build := testBuild(2) 275 build.KernelRepo = job.KernelRepo 276 build.KernelBranch = job.KernelBranch 277 build.KernelCommit = "deadf00d" 278 done = &dashapi.JobDoneReq{ 279 ID: job.ID, 280 Build: *build, 281 Log: []byte("bisect log 2"), 282 CrashTitle: "bisect crash title", 283 CrashLog: []byte("bisect crash log"), 284 CrashReport: []byte("bisect crash report"), 285 Commits: []dashapi.Commit{ 286 { 287 AuthorName: "Someone", 288 Author: "someone@somewhere.com", 289 Hash: "deadf00d", 290 Title: "kernel: fix a bug", 291 Date: time.Date(2000, 2, 9, 4, 5, 6, 7, time.UTC), 292 }, 293 }, 294 } 295 done.Build.ID = job.ID 296 ctx.ctx.advanceTime(time.Hour) 297 c.expectOK(ctx.client.JobDone(done)) 298 299 // Ensure the job is no longer created. 300 ctx.ctx.advanceTime(time.Hour) 301 job = ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 302 assert.Equal(t, job.ID, "") 303 304 msg := ctx.emailWithoutURLs() 305 c.expectEQ(msg.Body, `syzbot suspects this issue could be fixed by backporting the following commit: 306 307 commit deadf00d 308 git tree: upstream 309 Author: Someone <someone@somewhere.com> 310 Date: Wed Feb 9 04:05:06 2000 +0000 311 312 kernel: fix a bug 313 314 bisection log: %URL% 315 final oops: %URL% 316 console output: %URL% 317 kernel config: %URL% 318 dashboard link: %URL% 319 syz repro: %URL% 320 C reproducer: %URL% 321 322 323 Please keep in mind that other backports might be required as well. 324 325 For information about bisection process see: %URL%#bisection 326 `) 327 ctx.ctx.expectNoEmail() 328 329 info := ctx.fullBugInfo() 330 assert.NotNil(t, info.FixCandidate) 331 fix := info.FixCandidate 332 assert.Equal(t, "upstream", fix.KernelRepoAlias) 333 assert.NotNil(t, fix.BisectFix) 334 assert.NotNil(t, fix.BisectFix.Commit) 335 commit := fix.BisectFix.Commit 336 assert.Equal(t, "deadf00d", commit.Hash) 337 assert.Equal(t, "kernel: fix a bug", commit.Title) 338 339 // Ensure the bug is not automatically closed. 340 bug := ctx.loadBug() 341 assert.Len(t, bug.Commits, 0) 342 343 // Ensure the bug is present on the backports list page. 344 reply, err := ctx.ctx.AuthGET(AccessAdmin, "/tree-tests/backports") 345 c.expectOK(err) 346 assert.Contains(t, string(reply), treeTestCrashTitle) 347 assert.Contains(t, string(reply), "deadf00d") 348 349 // But don't show this to all users. 350 reply, err = ctx.ctx.AuthGET(AccessPublic, "/tree-tests/backports") 351 c.expectOK(err) 352 assert.NotContains(t, string(reply), treeTestCrashTitle) 353 354 // Check that we display it in another related namespace. 355 upstreamBuild := testBuild(100) 356 upstreamBuild.KernelRepo = "https://upstream.repo/repo" 357 upstreamBuild.KernelBranch = "upstream-master" 358 ctx.ctx.publicClient.UploadBuild(upstreamBuild) 359 reply, err = ctx.ctx.AuthGET(AccessAdmin, "/access-public-email/backports") 360 c.expectOK(err) 361 assert.Contains(t, string(reply), treeTestCrashTitle) 362 363 // .. but, again, not to everyone. 364 reply, err = ctx.ctx.AuthGET(AccessPublic, "/access-public-email/backports") 365 c.expectOK(err) 366 assert.NotContains(t, string(reply), treeTestCrashTitle) 367 368 // The bug must appear in commit poll. 369 commitPollResp, err := ctx.client.CommitPoll() 370 c.expectOK(err) 371 assert.Contains(t, commitPollResp.Commits, "kernel: fix a bug") 372 373 // Pretend that we have found a commit. 374 c.expectOK(ctx.client.UploadCommits([]dashapi.Commit{ 375 { 376 Hash: "newhash", 377 Title: "kernel: fix a bug", 378 AuthorName: "Someone", 379 Author: "someone@somewhere.com", 380 Date: time.Date(2000, 3, 4, 5, 6, 7, 8, time.UTC), 381 }, 382 })) 383 384 // An email must be sent. 385 msg = ctx.emailWithoutURLs() 386 fmt.Printf("%s", msg) 387 c.expectEQ(msg.Body, `The commit that was suspected to fix the issue was backported to the fuzzed 388 kernel trees. 389 390 commit newhash 391 Author: Someone <someone@somewhere.com> 392 Date: Sat Mar 4 05:06:07 2000 +0000 393 394 kernel: fix a bug 395 396 If you believe this is correct, please reply with 397 #syz fix: kernel: fix a bug 398 399 The commit was initially detected here: 400 401 commit deadf00d 402 git tree: upstream 403 Author: Someone <someone@somewhere.com> 404 Date: Wed Feb 9 04:05:06 2000 +0000 405 406 kernel: fix a bug 407 408 bisection log: %URL% 409 final oops: %URL% 410 console output: %URL% 411 kernel config: %URL% 412 dashboard link: %URL% 413 syz repro: %URL% 414 C reproducer: %URL% 415 `) 416 // Only one email. 417 ctx.ctx.expectNoEmail() 418 419 // The commit should disappear from the missing backports list. 420 reply, err = ctx.ctx.AuthGET(AccessAdmin, "/tree-tests/backports") 421 c.expectOK(err) 422 assert.NotContains(t, string(reply), treeTestCrashTitle) 423 assert.NotContains(t, string(reply), "deadf00d") 424 } 425 426 func TestNonfinalFixCandidateBisect(t *testing.T) { 427 c := NewCtx(t) 428 defer c.Close() 429 430 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 431 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 432 ctx.entries = []treeTestEntry{ 433 { 434 alias: `downstream`, 435 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 436 }, 437 { 438 alias: `lts`, 439 mergeAlias: `downstream`, 440 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 441 }, 442 { 443 alias: `upstream`, 444 // Ignore these jobs. 445 results: []treeTestEntryPeriod{}, 446 }, 447 } 448 ctx.jobTestDays = []int{10} 449 ctx.moveToDay(10) 450 ctx.reportToEmail() 451 ctx.ctx.advanceTime(time.Hour) 452 453 // Ensure the code does not fail. 454 job := ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 455 assert.Equal(t, "", job.ID) 456 } 457 458 func TestTreeBisectionBeforeOrigin(t *testing.T) { 459 c := NewCtx(t) 460 defer c.Close() 461 462 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 463 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 464 ctx.reportToEmail() 465 // Ensure the job is no longer created. 466 ctx.ctx.advanceTime(time.Hour) 467 job := ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{BisectFix: true}) 468 assert.Equal(t, "", job.ID) 469 } 470 471 func TestTreeOriginErrors(t *testing.T) { 472 c := NewCtx(t) 473 defer c.Close() 474 475 // Make sure testing works fine despite patch testing errors. 476 ctx := setUpTreeTest(c, downstreamUpstreamRepos) 477 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 478 ctx.entries = []treeTestEntry{ 479 { 480 alias: `downstream`, 481 results: []treeTestEntryPeriod{ 482 {fromDay: 0, result: treeTestCrash}, 483 }, 484 }, 485 { 486 alias: `lts`, 487 mergeAlias: `downstream`, 488 results: []treeTestEntryPeriod{ 489 {fromDay: 0, result: treeTestError}, 490 {fromDay: 16, result: treeTestCrash}, 491 }, 492 }, 493 { 494 alias: `upstream`, 495 results: []treeTestEntryPeriod{ 496 {fromDay: 0, result: treeTestError}, 497 {fromDay: 31, result: treeTestCrash}, 498 }, 499 }, 500 } 501 ctx.jobTestDays = []int{1, 16, 31} 502 ctx.moveToDay(1) 503 ctx.ensureLabels() // Not enough information yet. 504 // Lts got unbroken. 505 ctx.moveToDay(16) 506 ctx.ensureLabels(`origin:lts`) // We don't know any better so far. 507 // Upstream got unbroken. 508 ctx.moveToDay(31) 509 ctx.ensureLabels(`origin:upstream`) 510 c.expectEQ(ctx.entries[0].jobsDone, 0) 511 c.expectEQ(ctx.entries[1].jobsDone, 2) 512 c.expectEQ(ctx.entries[2].jobsDone, 3) 513 } 514 515 var downstreamUpstreamRepos = []KernelRepo{ 516 { 517 URL: `https://downstream.repo/repo`, 518 Branch: `master`, 519 Alias: `downstream`, 520 LabelIntroduced: `downstream`, 521 CommitInflow: []KernelRepoLink{ 522 { 523 Alias: `upstream`, 524 }, 525 { 526 Alias: `lts`, 527 Merge: true, 528 }, 529 }, 530 }, 531 { 532 URL: `https://lts.repo/repo`, 533 Branch: `lts-master`, 534 Alias: `lts`, 535 LabelIntroduced: `lts`, 536 CommitInflow: []KernelRepoLink{ 537 { 538 Alias: `upstream`, 539 Merge: false, 540 BisectFixes: true, 541 }, 542 }, 543 }, 544 { 545 URL: `https://upstream.repo/repo`, 546 Branch: `upstream-master`, 547 Alias: `upstream`, 548 LabelIntroduced: `upstream`, 549 }, 550 } 551 552 func TestOriginTreeNoMergeLts(t *testing.T) { 553 c := NewCtx(t) 554 defer c.Close() 555 556 ctx := setUpTreeTest(c, ltsUpstreamRepos) 557 ctx.uploadBug(`https://lts.repo/repo`, `lts-master`, dashapi.ReproLevelC) 558 ctx.entries = []treeTestEntry{ 559 { 560 alias: `lts`, 561 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 562 }, 563 { 564 alias: `upstream`, 565 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 566 }, 567 } 568 ctx.jobTestDays = []int{10} 569 ctx.moveToDay(10) 570 ctx.ensureLabels(`origin:lts-only`) 571 c.expectEQ(ctx.entries[0].jobsDone, 1) 572 c.expectEQ(ctx.entries[1].jobsDone, 1) 573 } 574 575 func TestOriginTreeNoMergeNoLabel(t *testing.T) { 576 c := NewCtx(t) 577 defer c.Close() 578 579 ctx := setUpTreeTest(c, ltsUpstreamRepos) 580 ctx.uploadBug(`https://lts.repo/repo`, `lts-master`, dashapi.ReproLevelC) 581 ctx.entries = []treeTestEntry{ 582 { 583 alias: `lts`, 584 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 585 }, 586 { 587 alias: `upstream`, 588 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 589 }, 590 } 591 ctx.jobTestDays = []int{10} 592 ctx.moveToDay(10) 593 ctx.ensureLabels() 594 // It should habe been enough to run jobs just once. 595 c.expectEQ(ctx.entries[0].jobsDone, 0) 596 c.expectEQ(ctx.entries[1].jobsDone, 1) 597 } 598 599 func TestTreeOriginRepoChanged(t *testing.T) { 600 c := NewCtx(t) 601 defer c.Close() 602 603 ctx := setUpTreeTest(c, ltsUpstreamRepos) 604 605 // First do tests from one repository. 606 ctx.uploadBug(`https://lts.repo/repo`, `lts-master`, dashapi.ReproLevelC) 607 ctx.entries = []treeTestEntry{ 608 { 609 alias: `lts`, 610 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 611 }, 612 { 613 alias: `upstream`, 614 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 615 }, 616 } 617 ctx.jobTestDays = []int{10, 20, 25, 30, 62} 618 ctx.moveToDay(10) 619 ctx.ensureLabels(`origin:lts-only`) 620 c.expectEQ(ctx.entries[0].jobsDone, 1) 621 c.expectEQ(ctx.entries[1].jobsDone, 1) 622 623 // Now update the repository. 624 ctx.updateRepos([]KernelRepo{ 625 { 626 URL: `https://new-lts.repo/repo`, 627 Branch: `lts-master`, 628 Alias: `lts`, 629 LabelIntroduced: `lts-only`, 630 ReportingPriority: 9, 631 CommitInflow: []KernelRepoLink{ 632 { 633 Alias: `upstream`, 634 Merge: false, 635 }, 636 }, 637 }, 638 { 639 URL: `https://upstream.repo/repo`, 640 Branch: `upstream-master`, 641 Alias: `upstream`, 642 }, 643 }) 644 ctx.entries = []treeTestEntry{ 645 { 646 alias: `lts`, 647 results: []treeTestEntryPeriod{ 648 {fromDay: 30, result: treeTestError}, 649 {fromDay: 60, result: treeTestCrash}, 650 }, 651 }, 652 { 653 alias: `upstream`, 654 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 655 }, 656 } 657 ctx.moveToDay(20) 658 ctx.ensureLabels(`origin:lts-only`) // No new builds -- nothing we can do. 659 660 // Upload a new manager build. 661 build := ctx.uploadBuild(`https://new-lts.repo/repo`, `lts-master`) 662 ctx.moveToDay(25) 663 ctx.ensureLabels(`origin:lts-only`) // Still nothing we can do, no crashes so far. 664 665 // Now upload a new crash. 666 ctx.uploadBuildCrash(build, dashapi.ReproLevelC) 667 ctx.moveToDay(30) 668 ctx.ensureLabels() // We are no longer sure about tags. 669 670 // After the new tree starts to build again, we can calculate the results again. 671 ctx.moveToDay(62) 672 ctx.ensureLabels(`origin:lts-only`) // We are no longer sure about tags. 673 c.expectEQ(ctx.entries[0].jobsDone, 2) 674 c.expectEQ(ctx.entries[1].jobsDone, 1) 675 } 676 677 var ltsUpstreamRepos = []KernelRepo{ 678 { 679 URL: `https://lts.repo/repo`, 680 Branch: `lts-master`, 681 Alias: `lts`, 682 LabelIntroduced: `lts-only`, 683 CommitInflow: []KernelRepoLink{ 684 { 685 Alias: `upstream`, 686 Merge: false, 687 }, 688 }, 689 }, 690 { 691 URL: `https://upstream.repo/repo`, 692 Branch: `upstream-master`, 693 Alias: `upstream`, 694 }, 695 } 696 697 func TestOriginNoNextTree(t *testing.T) { 698 c := NewCtx(t) 699 defer c.Close() 700 701 ctx := setUpTreeTest(c, upstreamNextRepos) 702 ctx.uploadBug(`https://upstream.repo/repo`, `upstream-master`, dashapi.ReproLevelC) 703 ctx.entries = []treeTestEntry{} 704 ctx.jobTestDays = []int{10} 705 ctx.moveToDay(10) 706 ctx.ensureLabels() 707 } 708 709 func TestOriginNoNextFixed(t *testing.T) { 710 c := NewCtx(t) 711 defer c.Close() 712 713 ctx := setUpTreeTest(c, upstreamNextRepos) 714 ctx.uploadBug(`https://next.repo/repo`, `next-master`, dashapi.ReproLevelC) 715 ctx.entries = []treeTestEntry{ 716 { 717 alias: `next`, 718 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 719 }, 720 { 721 alias: `upstream`, 722 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 723 }, 724 } 725 ctx.jobTestDays = []int{10} 726 ctx.moveToDay(10) 727 ctx.ensureLabels() 728 c.expectEQ(ctx.entries[0].jobsDone, 1) 729 c.expectEQ(ctx.entries[1].jobsDone, 1) 730 } 731 732 func TestOriginNoNext(t *testing.T) { 733 c := NewCtx(t) 734 defer c.Close() 735 736 ctx := setUpTreeTest(c, upstreamNextRepos) 737 ctx.uploadBug(`https://next.repo/repo`, `next-master`, dashapi.ReproLevelC) 738 ctx.entries = []treeTestEntry{ 739 { 740 alias: `next`, 741 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 742 }, 743 { 744 alias: `upstream`, 745 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 746 }, 747 } 748 ctx.jobTestDays = []int{10} 749 ctx.moveToDay(10) 750 ctx.ensureLabels() 751 c.expectEQ(ctx.entries[0].jobsDone, 0) 752 c.expectEQ(ctx.entries[1].jobsDone, 1) 753 } 754 755 func TestOriginNext(t *testing.T) { 756 c := NewCtx(t) 757 defer c.Close() 758 759 ctx := setUpTreeTest(c, upstreamNextRepos) 760 ctx.uploadBug(`https://next.repo/repo`, `next-master`, dashapi.ReproLevelC) 761 ctx.entries = []treeTestEntry{ 762 { 763 alias: `next`, 764 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 765 }, 766 { 767 alias: `upstream`, 768 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestOK}}, 769 }, 770 } 771 ctx.jobTestDays = []int{10} 772 ctx.moveToDay(10) 773 ctx.ensureLabels(`origin:next`) 774 c.expectEQ(ctx.entries[0].jobsDone, 1) 775 c.expectEQ(ctx.entries[1].jobsDone, 1) 776 } 777 778 var upstreamNextRepos = []KernelRepo{ 779 { 780 URL: `https://upstream.repo/repo`, 781 Branch: `upstream-master`, 782 Alias: `upstream`, 783 CommitInflow: []KernelRepoLink{ 784 { 785 Alias: `next`, 786 Merge: false, 787 }, 788 }, 789 }, 790 { 791 URL: `https://next.repo/repo`, 792 Branch: `next-master`, 793 Alias: `next`, 794 LabelReached: `next`, 795 }, 796 } 797 798 func TestMissingLtsBackport(t *testing.T) { 799 c := NewCtx(t) 800 defer c.Close() 801 802 ctx := setUpTreeTest(c, downstreamUpstreamBackports) 803 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 804 ctx.entries = []treeTestEntry{ 805 { 806 alias: `downstream`, 807 results: []treeTestEntryPeriod{ 808 {fromDay: 0, result: treeTestCrash}, 809 }, 810 }, 811 { 812 alias: `lts`, 813 mergeAlias: `downstream`, 814 results: []treeTestEntryPeriod{ 815 {fromDay: 0, result: treeTestCrash}, 816 }, 817 }, 818 { 819 alias: `lts`, 820 results: []treeTestEntryPeriod{ 821 {fromDay: 0, result: treeTestCrash}, 822 {fromDay: 46, result: treeTestOK}, 823 }, 824 }, 825 { 826 alias: `upstream`, 827 results: []treeTestEntryPeriod{ 828 {fromDay: 0, result: treeTestCrash}, 829 }, 830 }, 831 } 832 ctx.jobTestDays = []int{0, 46} 833 ctx.moveToDay(46) 834 ctx.ensureLabels(`missing-backport`) 835 c.expectEQ(ctx.entries[0].jobsDone, 1) 836 c.expectEQ(ctx.entries[1].jobsDone, 1) 837 c.expectEQ(ctx.entries[1].jobsDone, 1) 838 c.expectEQ(ctx.entries[1].jobsDone, 1) 839 } 840 841 func TestMissingUpstreamBackport(t *testing.T) { 842 c := NewCtx(t) 843 defer c.Close() 844 845 ctx := setUpTreeTest(c, downstreamUpstreamBackports) 846 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 847 ctx.entries = []treeTestEntry{ 848 { 849 alias: `downstream`, 850 results: []treeTestEntryPeriod{ 851 {fromDay: 0, result: treeTestCrash}, 852 }, 853 }, 854 { 855 alias: `lts`, 856 results: []treeTestEntryPeriod{ 857 {fromDay: 0, result: treeTestCrash}, 858 }, 859 }, 860 { 861 alias: `upstream`, 862 results: []treeTestEntryPeriod{ 863 {fromDay: 0, result: treeTestCrash}, 864 {fromDay: 31, result: treeTestOK}, 865 }, 866 }, 867 } 868 ctx.jobTestDays = []int{0, 46} 869 ctx.moveToDay(46) 870 ctx.ensureLabels(`missing-backport`) 871 c.expectEQ(ctx.entries[0].jobsDone, 1) 872 c.expectEQ(ctx.entries[1].jobsDone, 2) 873 c.expectEQ(ctx.entries[1].jobsDone, 2) 874 } 875 876 func TestNotMissingBackport(t *testing.T) { 877 c := NewCtx(t) 878 defer c.Close() 879 880 ctx := setUpTreeTest(c, downstreamUpstreamBackports) 881 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 882 ctx.entries = []treeTestEntry{ 883 { 884 alias: `downstream`, 885 results: []treeTestEntryPeriod{ 886 {fromDay: 0, result: treeTestCrash}, 887 }, 888 }, 889 { 890 alias: `lts`, 891 mergeAlias: `downstream`, 892 results: []treeTestEntryPeriod{ 893 {fromDay: 0, result: treeTestOK}, 894 }, 895 }, 896 { 897 alias: `lts`, 898 results: []treeTestEntryPeriod{ 899 {fromDay: 0, result: treeTestOK}, 900 }, 901 }, 902 { 903 alias: `upstream`, 904 results: []treeTestEntryPeriod{ 905 {fromDay: 0, result: treeTestCrash}, 906 }, 907 }, 908 } 909 ctx.jobTestDays = []int{0, 46} 910 ctx.moveToDay(46) 911 ctx.ensureLabels() 912 c.expectEQ(ctx.entries[0].jobsDone, 0) 913 c.expectEQ(ctx.entries[1].jobsDone, 1) 914 c.expectEQ(ctx.entries[2].jobsDone, 1) 915 c.expectEQ(ctx.entries[3].jobsDone, 2) 916 } 917 918 var downstreamUpstreamBackports = []KernelRepo{ 919 { 920 URL: `https://downstream.repo/repo`, 921 Branch: `master`, 922 Alias: `downstream`, 923 CommitInflow: []KernelRepoLink{ 924 { 925 Alias: `lts`, 926 Merge: true, 927 }, 928 { 929 Alias: `upstream`, 930 }, 931 }, 932 DetectMissingBackports: true, 933 }, 934 { 935 URL: `https://lts.repo/repo`, 936 Branch: `lts-master`, 937 Alias: `lts`, 938 CommitInflow: []KernelRepoLink{ 939 { 940 Alias: `upstream`, 941 Merge: false, 942 }, 943 }, 944 }, 945 { 946 URL: `https://upstream.repo/repo`, 947 Branch: `upstream-master`, 948 Alias: `upstream`, 949 }, 950 } 951 952 func TestTreeConfigAppend(t *testing.T) { 953 c := NewCtx(t) 954 defer c.Close() 955 956 ctx := setUpTreeTest(c, []KernelRepo{ 957 { 958 URL: `https://downstream.repo/repo`, 959 Branch: `master`, 960 Alias: `downstream`, 961 CommitInflow: []KernelRepoLink{ 962 { 963 Alias: `lts`, 964 Merge: true, 965 }, 966 }, 967 LabelIntroduced: `downstream`, 968 }, 969 { 970 URL: `https://lts.repo/repo`, 971 Branch: `lts-master`, 972 Alias: `lts`, 973 LabelIntroduced: `lts`, 974 AppendConfig: "\nCONFIG_TEST=y", 975 }, 976 }) 977 ctx.uploadBug(`https://downstream.repo/repo`, `master`, dashapi.ReproLevelC) 978 ctx.entries = []treeTestEntry{ 979 { 980 alias: `downstream`, 981 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 982 }, 983 { 984 alias: `lts`, 985 mergeAlias: `downstream`, 986 results: []treeTestEntryPeriod{{fromDay: 0, result: treeTestCrash}}, 987 }, 988 } 989 ctx.jobTestDays = []int{10} 990 tested := false 991 ctx.validateJob = func(resp *dashapi.JobPollResp) { 992 if resp.KernelBranch == "lts-master" { 993 tested = true 994 assert.Contains(t, string(resp.KernelConfig), "\nCONFIG_TEST=y") 995 } 996 } 997 ctx.moveToDay(10) 998 assert.True(t, tested) 999 } 1000 1001 func setUpTreeTest(ctx *Ctx, repos []KernelRepo) *treeTestCtx { 1002 ret := &treeTestCtx{ 1003 ctx: ctx, 1004 client: ctx.makeClient(clientTreeTests, keyTreeTests, true), 1005 manager: "test-manager", 1006 } 1007 ret.updateRepos(repos) 1008 return ret 1009 } 1010 1011 type treeTestCtx struct { 1012 ctx *Ctx 1013 client *apiClient 1014 bug *Bug 1015 bugReport *dashapi.BugReport 1016 start time.Time 1017 entries []treeTestEntry 1018 perAlias map[string]KernelRepo 1019 jobTestDays []int 1020 manager string 1021 validateJob func(*dashapi.JobPollResp) 1022 } 1023 1024 func (ctx *treeTestCtx) now() time.Time { 1025 // Yep, that's a bit too much repetition. 1026 return timeNow(ctx.ctx.ctx) 1027 } 1028 1029 func (ctx *treeTestCtx) updateRepos(repos []KernelRepo) { 1030 checkKernelRepos("tree-tests", ctx.ctx.config().Namespaces["tree-tests"], repos) 1031 ctx.perAlias = map[string]KernelRepo{} 1032 for _, repo := range repos { 1033 ctx.perAlias[repo.Alias] = repo 1034 } 1035 ctx.ctx.setKernelRepos("tree-tests", repos) 1036 } 1037 1038 func (ctx *treeTestCtx) uploadBuild(repo, branch string) *dashapi.Build { 1039 build := testBuild(1) 1040 build.ID = fmt.Sprintf("%d", ctx.now().Unix()) 1041 build.Manager = ctx.manager 1042 build.KernelRepo = repo 1043 build.KernelBranch = branch 1044 build.KernelCommit = build.ID 1045 ctx.client.UploadBuild(build) 1046 return build 1047 } 1048 1049 const treeTestCrashTitle = "cross-tree bug title" 1050 1051 func (ctx *treeTestCtx) uploadBuildCrash(build *dashapi.Build, lvl dashapi.ReproLevel) { 1052 crash := testCrash(build, 1) 1053 crash.Title = treeTestCrashTitle 1054 if lvl > dashapi.ReproLevelNone { 1055 crash.ReproSyz = []byte("getpid()") 1056 } 1057 if lvl == dashapi.ReproLevelC { 1058 crash.ReproC = []byte("getpid()") 1059 } 1060 ctx.client.ReportCrash(crash) 1061 if ctx.bug == nil || ctx.bug.ReproLevel < lvl { 1062 ctx.bugReport = ctx.client.pollBug() 1063 if ctx.bug == nil { 1064 bug, _, err := findBugByReportingID(ctx.ctx.ctx, ctx.bugReport.ID) 1065 ctx.ctx.expectOK(err) 1066 ctx.bug = bug 1067 } 1068 } 1069 } 1070 1071 func (ctx *treeTestCtx) uploadBug(repo, branch string, lvl dashapi.ReproLevel) { 1072 build := ctx.uploadBuild(repo, branch) 1073 ctx.uploadBuildCrash(build, lvl) 1074 } 1075 1076 func (ctx *treeTestCtx) moveToDay(tillDay int) { 1077 ctx.ctx.t.Helper() 1078 if ctx.start.IsZero() { 1079 ctx.start = ctx.now() 1080 } 1081 for _, seqDay := range ctx.jobTestDays { 1082 if seqDay > tillDay { 1083 break 1084 } 1085 now := ctx.now() 1086 day := ctx.start.Add(time.Hour * 24 * time.Duration(seqDay)) 1087 if day.Before(now) || ctx.start != ctx.now() && day.Equal(now) { 1088 continue 1089 } 1090 ctx.ctx.advanceTime(day.Sub(now)) 1091 ctx.ctx.t.Logf("executing jobs on day %d", seqDay) 1092 // Execute jobs until they exist. 1093 for { 1094 pollResp := ctx.client.pollSpecificJobs(ctx.manager, dashapi.ManagerJobs{ 1095 TestPatches: true, 1096 }) 1097 if pollResp.ID == "" { 1098 break 1099 } 1100 if ctx.validateJob != nil { 1101 ctx.validateJob(pollResp) 1102 } 1103 ctx.ctx.advanceTime(time.Minute) 1104 ctx.doJob(pollResp, seqDay) 1105 } 1106 } 1107 } 1108 1109 func (ctx *treeTestCtx) doJob(resp *dashapi.JobPollResp, day int) { 1110 respValues := []string{ 1111 resp.KernelRepo, 1112 resp.KernelBranch, 1113 resp.MergeBaseRepo, 1114 resp.MergeBaseBranch, 1115 } 1116 sort.Strings(respValues) 1117 var found *treeTestEntry 1118 for i, entry := range ctx.entries { 1119 entryValues := []string{ 1120 ctx.perAlias[entry.alias].URL, 1121 ctx.perAlias[entry.alias].Branch, 1122 } 1123 if entry.mergeAlias != "" { 1124 entryValues = append(entryValues, 1125 ctx.perAlias[entry.mergeAlias].URL, 1126 ctx.perAlias[entry.mergeAlias].Branch) 1127 } else { 1128 entryValues = append(entryValues, "", "") 1129 } 1130 sort.Strings(entryValues) 1131 if reflect.DeepEqual(respValues, entryValues) { 1132 found = &ctx.entries[i] 1133 break 1134 } 1135 } 1136 if found == nil { 1137 ctx.ctx.t.Fatalf("unknown job request: %#v", resp) 1138 return // to avoid staticcheck false positive about nil deref 1139 } 1140 // Figure out what should the result be. 1141 result := treeTestOK 1142 build := testBuild(1) 1143 var anyFound bool 1144 for _, item := range found.results { 1145 if day >= item.fromDay { 1146 result = item.result 1147 build.KernelCommit = item.commit 1148 anyFound = true 1149 } 1150 } 1151 if !anyFound { 1152 // Just ignore the job. 1153 return 1154 } 1155 if build.KernelCommit == "" { 1156 build.KernelCommit = strings.Repeat("f", 40)[:40] 1157 } 1158 build.KernelRepo = resp.KernelRepo 1159 build.KernelBranch = resp.KernelBranch 1160 build.ID = fmt.Sprintf("%s_%s_%s_%d", resp.KernelRepo, resp.KernelBranch, resp.KernelCommit, day) 1161 jobDoneReq := &dashapi.JobDoneReq{ 1162 ID: resp.ID, 1163 Build: *build, 1164 } 1165 switch result { 1166 case treeTestOK: 1167 case treeTestCrash: 1168 jobDoneReq.CrashTitle = "crash title" 1169 jobDoneReq.CrashLog = []byte("test crash log") 1170 jobDoneReq.CrashReport = []byte("test crash report") 1171 case treeTestError: 1172 jobDoneReq.Error = []byte("failed to apply patch") 1173 } 1174 found.jobsDone++ 1175 ctx.ctx.expectOK(ctx.client.JobDone(jobDoneReq)) 1176 } 1177 1178 func (ctx *treeTestCtx) ensureLabels(labels ...string) { 1179 ctx.ctx.t.Helper() 1180 bug := ctx.loadBug() 1181 var bugLabels []string 1182 for _, item := range bug.Labels { 1183 bugLabels = append(bugLabels, item.String()) 1184 } 1185 assert.ElementsMatch(ctx.ctx.t, labels, bugLabels) 1186 } 1187 1188 func (ctx *treeTestCtx) loadBug() *Bug { 1189 ctx.ctx.t.Helper() 1190 if ctx.bug == nil { 1191 ctx.ctx.t.Fatalf("no bug has been created so far") 1192 } 1193 bug := new(Bug) 1194 ctx.ctx.expectOK(db.Get(ctx.ctx.ctx, ctx.bug.key(ctx.ctx.ctx), bug)) 1195 ctx.bug = bug 1196 return bug 1197 } 1198 1199 func (ctx *treeTestCtx) bugLink() string { 1200 return fmt.Sprintf("/bug?id=%v", ctx.bug.key(ctx.ctx.ctx).StringID()) 1201 } 1202 1203 func (ctx *treeTestCtx) reportToEmail() *aemail.Message { 1204 ctx.client.updateBug(ctx.bugReport.ID, dashapi.BugStatusUpstream, "") 1205 return ctx.ctx.pollEmailBug() 1206 } 1207 1208 func (ctx *treeTestCtx) fullBugInfo() *dashapi.FullBugInfo { 1209 info, err := ctx.client.LoadFullBug(&dashapi.LoadFullBugReq{ 1210 BugID: ctx.bugReport.ID, 1211 }) 1212 ctx.ctx.expectOK(err) 1213 return info 1214 } 1215 1216 var urlRe = regexp.MustCompile(`(https?://[\w\./\?\=&]+)`) 1217 1218 func (ctx *treeTestCtx) emailWithoutURLs() *aemail.Message { 1219 msg := ctx.ctx.pollEmailBug() 1220 msg.Body = urlRe.ReplaceAllString(msg.Body, "%URL%") 1221 return msg 1222 } 1223 1224 type treeTestEntry struct { 1225 alias string 1226 mergeAlias string 1227 results []treeTestEntryPeriod 1228 jobsDone int 1229 } 1230 1231 type treeTestResult string 1232 1233 const ( 1234 treeTestCrash treeTestResult = "crash" 1235 treeTestOK treeTestResult = "ok" 1236 treeTestError treeTestResult = "error" 1237 ) 1238 1239 type treeTestEntryPeriod struct { 1240 fromDay int 1241 result treeTestResult 1242 commit string 1243 } 1244 1245 func TestRepoGraph(t *testing.T) { 1246 g, err := makeRepoGraph(downstreamUpstreamRepos) 1247 if err != nil { 1248 t.Fatal(err) 1249 } 1250 1251 downstream := g.nodeByAlias(`downstream`) 1252 lts := g.nodeByAlias(`lts`) 1253 upstream := g.nodeByAlias(`upstream`) 1254 1255 // Test the downstream node. 1256 if diff := cmp.Diff(map[*repoNode]bool{ 1257 lts: true, 1258 upstream: false, 1259 }, downstream.reachable(true)); diff != "" { 1260 t.Fatal(diff) 1261 } 1262 if diff := cmp.Diff(map[*repoNode]bool{}, downstream.reachable(false)); diff != "" { 1263 t.Fatal(diff) 1264 } 1265 1266 // Test the lts node. 1267 if diff := cmp.Diff(map[*repoNode]bool{ 1268 upstream: false, 1269 }, lts.reachable(true)); diff != "" { 1270 t.Fatal(diff) 1271 } 1272 if diff := cmp.Diff(map[*repoNode]bool{ 1273 downstream: true, 1274 }, lts.reachable(false)); diff != "" { 1275 t.Fatal(diff) 1276 } 1277 1278 // Test the upstream node. 1279 if diff := cmp.Diff(map[*repoNode]bool{}, upstream.reachable(true)); diff != "" { 1280 t.Fatal(diff) 1281 } 1282 if diff := cmp.Diff(map[*repoNode]bool{ 1283 downstream: false, 1284 lts: false, 1285 }, upstream.reachable(false)); diff != "" { 1286 t.Fatal(diff) 1287 } 1288 } 1289 1290 func TestRepoGraphMergeFirst(t *testing.T) { 1291 // Test whether we prioritize merge links. 1292 g, err := makeRepoGraph([]KernelRepo{ 1293 { 1294 URL: `https://downstream.repo/repo`, 1295 Branch: `master`, 1296 Alias: `downstream`, 1297 CommitInflow: []KernelRepoLink{ 1298 { 1299 Alias: `upstream`, 1300 Merge: false, 1301 }, 1302 { 1303 Alias: `lts`, 1304 Merge: true, 1305 }, 1306 }, 1307 }, 1308 { 1309 URL: `https://lts.repo/repo`, 1310 Branch: `lts-master`, 1311 Alias: `lts`, 1312 CommitInflow: []KernelRepoLink{ 1313 { 1314 Alias: `upstream`, 1315 Merge: true, 1316 }, 1317 }, 1318 }, 1319 { 1320 URL: `https://upstream.repo/repo`, 1321 Branch: `upstream-master`, 1322 Alias: `upstream`, 1323 }, 1324 }) 1325 if err != nil { 1326 t.Fatal(err) 1327 } 1328 1329 downstream := g.nodeByAlias(`downstream`) 1330 lts := g.nodeByAlias(`lts`) 1331 upstream := g.nodeByAlias(`upstream`) 1332 1333 // Test the downstream node. 1334 if diff := cmp.Diff(map[*repoNode]bool{ 1335 lts: true, 1336 upstream: true, 1337 }, downstream.reachable(true)); diff != "" { 1338 t.Fatal(diff) 1339 } 1340 if diff := cmp.Diff(map[*repoNode]bool{}, downstream.reachable(false)); diff != "" { 1341 t.Fatal(diff) 1342 } 1343 }