github.com/pachyderm/pachyderm@v1.13.4/src/server/worker/pipeline/transform/chain/chain_test.go (about) 1 package chain 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 "time" 8 9 "golang.org/x/sync/errgroup" 10 11 "github.com/pachyderm/pachyderm/src/client/pfs" 12 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 13 "github.com/pachyderm/pachyderm/src/client/pkg/require" 14 "github.com/pachyderm/pachyderm/src/server/worker/common" 15 "github.com/pachyderm/pachyderm/src/server/worker/datum" 16 ) 17 18 type testHasher struct{} 19 20 func (th *testHasher) Hash(inputs []*common.Input) string { 21 return common.HashDatum("", "", inputs) 22 } 23 24 func makeIndex() map[string]string { 25 hasher := &testHasher{} 26 result := make(map[string]string) 27 names := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"} 28 for _, name := range names { 29 hash := hasher.Hash(datumToInputs(name)) 30 result[hash] = name 31 } 32 return result 33 } 34 35 var datumIndex = makeIndex() 36 37 /* Useful functions for debugging, commented out to appease lint 38 39 func printState(jc *jobChain) { 40 jc.mutex.Lock() 41 defer jc.mutex.Unlock() 42 43 for i, jdi := range jc.jobs { 44 flags := "" 45 if jdi.additiveOnly { 46 flags += " additive" 47 } 48 if jdi.finished { 49 if jdi.allDatums != nil { 50 flags += " succeeded" 51 } else { 52 flags += " failed" 53 } 54 } 55 fmt.Printf("Job %d:%s\n", i, flags) 56 57 ancestors := []int{} 58 for _, ancestor := range jdi.ancestors { 59 index, err := jc.indexOf(ancestor.data) 60 if err != nil { 61 index = -1 62 } 63 ancestors = append(ancestors, index) 64 } 65 fmt.Printf("ancestors: %v\n", ancestors) 66 67 printDatumSet(jc, "allDatums", jdi.allDatums) 68 printDatumSet(jc, "yielding", jdi.yielding) 69 printDatumSet(jc, "yielded", jdi.yielded) 70 } 71 } 72 73 func printDatumSet(jc *jobChain, name string, set DatumSet) { 74 arr := []string{} 75 for hash, count := range set { 76 name := datumIndex[hash] 77 if name == "" { 78 name = "unknown" 79 } 80 arr = append(arr, fmt.Sprintf("%s: %d", name, count)) 81 } 82 sort.Strings(arr) 83 fmt.Printf(" %s (%d): %v\n", name, len(set), arr) 84 } 85 */ 86 87 type testIterator struct { 88 index int 89 inputs [][]*common.Input 90 } 91 92 func (ti *testIterator) Reset() { 93 ti.index = -1 94 } 95 96 func (ti *testIterator) Len() int { 97 return len(ti.inputs) 98 } 99 100 func (ti *testIterator) Next() bool { 101 if ti.index < len(ti.inputs) { 102 ti.index++ 103 } 104 105 return ti.index < len(ti.inputs) 106 } 107 108 func (ti *testIterator) Datum() []*common.Input { 109 if ti.index >= len(ti.inputs) { 110 return nil 111 } 112 return ti.inputs[ti.index] 113 } 114 115 func (ti *testIterator) DatumN(n int) []*common.Input { 116 return ti.inputs[n] 117 } 118 119 // Convert a test-friendly string to a real fake inputs array 120 func datumToInputs(name string) []*common.Input { 121 return []*common.Input{{ 122 Name: "inputRepo", 123 FileInfo: &pfs.FileInfo{ 124 File: &pfs.File{Path: name}, 125 Hash: []byte(name), 126 }, 127 }} 128 } 129 130 func inputsToDatum(inputs []*common.Input) (string, error) { 131 if len(inputs) != 1 { 132 return "", errors.New("should only have 1 input for test datums") 133 } 134 return inputs[0].FileInfo.File.Path, nil 135 } 136 137 func newTestChain(t *testing.T, datums []string) JobChain { 138 hasher := &testHasher{} 139 baseDatums := datumsToSet(datums) 140 return NewJobChain(hasher, baseDatums) 141 } 142 143 func datumsToInputs(datums []string) [][]*common.Input { 144 inputs := [][]*common.Input{} 145 for _, datum := range datums { 146 inputs = append(inputs, datumToInputs(datum)) 147 } 148 return inputs 149 } 150 151 func datumsToSet(datums []string) DatumSet { 152 hasher := &testHasher{} 153 result := make(DatumSet) 154 for _, datum := range datums { 155 result[hasher.Hash(datumToInputs(datum))]++ 156 } 157 return result 158 } 159 160 func setToDatums(t *testing.T, datumSet DatumSet) []string { 161 result := []string{} 162 for hash, count := range datumSet { 163 name := datumIndex[hash] 164 require.NotEqual(t, "", name) 165 for i := int64(0); i < count; i++ { 166 result = append(result, name) 167 } 168 } 169 return result 170 } 171 172 func newTestIterator(datums []string) datum.Iterator { 173 return &testIterator{inputs: datumsToInputs(datums)} 174 } 175 176 type testJob struct { 177 dit datum.Iterator 178 } 179 180 func newTestJob(datums []string) JobData { 181 return &testJob{dit: newTestIterator(datums)} 182 } 183 184 func (tj *testJob) Iterator() (datum.Iterator, error) { 185 return tj.dit, nil 186 } 187 188 func requireChainEmpty(t *testing.T, chain JobChain, expectedBaseDatums []string) { 189 jc := chain.(*jobChain) 190 require.Equal(t, 1, len(jc.jobs)) 191 require.ElementsEqual(t, expectedBaseDatums, setToDatums(t, jc.jobs[0].allDatums)) 192 } 193 194 func requireIteratorContents(t *testing.T, jdi JobDatumIterator, expected []string) { 195 count, err := jdi.NextBatch(context.Background()) 196 require.NoError(t, err) 197 require.Equal(t, int64(len(expected)), count) 198 199 found := []string{} 200 for range expected { 201 inputs, _ := jdi.NextDatum() 202 datum, err := inputsToDatum(inputs) 203 require.NoError(t, err) 204 found = append(found, datum) 205 } 206 require.ElementsEqual(t, expected, found) 207 requireIteratorDone(t, jdi) 208 } 209 210 func requireIteratorDone(t *testing.T, jdi JobDatumIterator) { 211 count, err := jdi.NextBatch(context.Background()) 212 require.NoError(t, err) 213 require.Equal(t, int64(0), count) 214 } 215 216 func TestEmptyBase(t *testing.T) { 217 jobDatums := []string{"a", "b"} 218 chain := newTestChain(t, []string{}) 219 job := newTestJob(jobDatums) 220 jdi, err := chain.Start(job) 221 require.NoError(t, err) 222 requireIteratorContents(t, jdi, jobDatums) 223 224 require.NoError(t, chain.Succeed(job)) 225 requireChainEmpty(t, chain, jobDatums) 226 } 227 228 func TestAdditiveOnBase(t *testing.T) { 229 jobDatums := []string{"a", "b", "c"} 230 chain := newTestChain(t, []string{"a"}) 231 job := newTestJob(jobDatums) 232 jdi, err := chain.Start(job) 233 require.NoError(t, err) 234 requireIteratorContents(t, jdi, []string{"b", "c"}) 235 236 require.NoError(t, chain.Succeed(job)) 237 requireChainEmpty(t, chain, jobDatums) 238 } 239 240 func TestSubtractiveOnBase(t *testing.T) { 241 jobDatums := []string{"a", "c"} 242 chain := newTestChain(t, []string{"a", "b", "c"}) 243 job := newTestJob(jobDatums) 244 jdi, err := chain.Start(job) 245 require.NoError(t, err) 246 requireIteratorContents(t, jdi, jobDatums) 247 248 require.NoError(t, chain.Succeed(job)) 249 requireChainEmpty(t, chain, jobDatums) 250 } 251 252 func TestAdditiveSubtractiveOnBase(t *testing.T) { 253 jobDatums := []string{"b", "c", "d", "e"} 254 chain := newTestChain(t, []string{"a", "b", "c"}) 255 job := newTestJob(jobDatums) 256 jdi, err := chain.Start(job) 257 require.NoError(t, err) 258 requireIteratorContents(t, jdi, jobDatums) 259 260 require.NoError(t, chain.Succeed(job)) 261 requireChainEmpty(t, chain, jobDatums) 262 } 263 264 // Read from a channel until we have the expected datums, then verify they 265 // are correct, then make sure the channel doesn't have anything else. 266 func requireDatums(t *testing.T, datumChan <-chan string, expected []string) { 267 // Recvs should be near-instant, but set a decently long timeout to avoid flakiness 268 actual := []string{} 269 loop: 270 for range expected { 271 select { 272 case x, ok := <-datumChan: 273 if !ok { 274 require.ElementsEqual(t, expected, actual) 275 } 276 actual = append(actual, x) 277 case <-time.After(time.Second): 278 break loop 279 } 280 } 281 require.ElementsEqual(t, expected, actual) 282 283 select { 284 case x, ok := <-datumChan: 285 require.False(t, ok, "datum channel contains extra datum: %s", x) 286 default: 287 } 288 } 289 290 func requireChannelClosed(t *testing.T, c <-chan string) { 291 select { 292 case x, ok := <-c: 293 require.False(t, ok, "datum channel should be closed, but found extra datum: %s", x) 294 case <-time.After(time.Second): 295 require.True(t, false, "datum channel should be closed, but it is blocked") 296 } 297 } 298 299 func requireChannelBlocked(t *testing.T, c <-chan string) { 300 select { 301 case x, ok := <-c: 302 require.True(t, ok, "datum channel should be blocked, but it is closed") 303 require.True(t, false, "datum channel should be blocked, but it contains datum: %s", x) 304 default: 305 } 306 } 307 308 func superviseTestJobWithError( 309 ctx context.Context, 310 eg *errgroup.Group, 311 jdi JobDatumIterator, 312 expectedErr string, 313 ) <-chan string { 314 datumsChan := make(chan string) 315 eg.Go(func() (retErr error) { 316 defer func() { 317 if retErr != nil && expectedErr != "" && strings.Contains(retErr.Error(), expectedErr) { 318 retErr = nil 319 } 320 }() 321 322 defer close(datumsChan) 323 for { 324 count, err := jdi.NextBatch(ctx) 325 if err != nil { 326 return err 327 } 328 if count == 0 { 329 return nil 330 } 331 332 for i := int64(0); i < count; i++ { 333 inputs, _ := jdi.NextDatum() 334 datum, err := inputsToDatum(inputs) 335 if err != nil { 336 return err 337 } 338 339 datumsChan <- datum 340 } 341 } 342 }) 343 344 return datumsChan 345 } 346 347 func superviseTestJob(ctx context.Context, eg *errgroup.Group, jdi JobDatumIterator) <-chan string { 348 return superviseTestJobWithError(ctx, eg, jdi, "") 349 } 350 351 // Job 1: ABCD -> 1. Succeed 352 // Job 2: CDEF -> 2. Succeed 353 // Job 3: AB DE GH -> 3. Succeed 354 func TestSuccess(t *testing.T) { 355 chain := newTestChain(t, []string{}) 356 job1 := newTestJob([]string{"a", "b", "c", "d"}) 357 job2 := newTestJob([]string{"c", "d", "e", "f"}) 358 job3 := newTestJob([]string{"a", "b", "d", "e", "g", "h"}) 359 360 eg, ctx := errgroup.WithContext(context.Background()) 361 362 jdi1, err := chain.Start(job1) 363 require.NoError(t, err) 364 datums1 := superviseTestJob(ctx, eg, jdi1) 365 366 jdi2, err := chain.Start(job2) 367 require.NoError(t, err) 368 datums2 := superviseTestJob(ctx, eg, jdi2) 369 370 jdi3, err := chain.Start(job3) 371 require.NoError(t, err) 372 datums3 := superviseTestJob(ctx, eg, jdi3) 373 374 requireDatums(t, datums1, []string{"a", "b", "c", "d"}) 375 requireDatums(t, datums2, []string{"e", "f"}) 376 requireDatums(t, datums3, []string{"g", "h"}) 377 requireChannelClosed(t, datums1) 378 requireChannelBlocked(t, datums2) 379 requireChannelBlocked(t, datums3) 380 381 require.NoError(t, chain.Succeed(job1)) 382 requireDatums(t, datums2, []string{"c", "d"}) 383 requireDatums(t, datums3, []string{"a", "b"}) 384 requireChannelClosed(t, datums2) 385 386 require.NoError(t, chain.Succeed(job2)) 387 requireDatums(t, datums3, []string{"d", "e"}) 388 requireChannelClosed(t, datums3) 389 390 require.NoError(t, chain.Succeed(job3)) 391 require.NoError(t, eg.Wait()) 392 393 requireChainEmpty(t, chain, []string{"a", "b", "d", "e", "g", "h"}) 394 } 395 396 // Job 1: ABCD -> 1. Fail 397 // Job 2: CDEF -> 2. Fail 398 // Job 3: AB DE GH -> 3. Succeed 399 func TestFail(t *testing.T) { 400 chain := newTestChain(t, []string{}) 401 job1 := newTestJob([]string{"a", "b", "c", "d"}) 402 job2 := newTestJob([]string{"c", "d", "e", "f"}) 403 job3 := newTestJob([]string{"a", "b", "d", "e", "g", "h"}) 404 405 eg, ctx := errgroup.WithContext(context.Background()) 406 407 jdi1, err := chain.Start(job1) 408 require.NoError(t, err) 409 datums1 := superviseTestJob(ctx, eg, jdi1) 410 411 jdi2, err := chain.Start(job2) 412 require.NoError(t, err) 413 datums2 := superviseTestJob(ctx, eg, jdi2) 414 415 jdi3, err := chain.Start(job3) 416 require.NoError(t, err) 417 datums3 := superviseTestJob(ctx, eg, jdi3) 418 419 requireDatums(t, datums1, []string{"a", "b", "c", "d"}) 420 requireDatums(t, datums2, []string{"e", "f"}) 421 requireDatums(t, datums3, []string{"g", "h"}) 422 requireChannelClosed(t, datums1) 423 requireChannelBlocked(t, datums2) 424 requireChannelBlocked(t, datums3) 425 426 require.NoError(t, chain.Fail(job1)) 427 requireDatums(t, datums2, []string{"c", "d"}) 428 requireDatums(t, datums3, []string{"a", "b"}) 429 requireChannelClosed(t, datums2) 430 431 require.NoError(t, chain.Fail(job2)) 432 requireDatums(t, datums3, []string{"d", "e"}) 433 requireChannelClosed(t, datums3) 434 435 require.NoError(t, chain.Succeed(job3)) 436 require.NoError(t, eg.Wait()) 437 438 requireChainEmpty(t, chain, []string{"a", "b", "d", "e", "g", "h"}) 439 } 440 441 // Job 1: AB -> 1. Succeed 442 // Job 2: ABC -> 2. Succeed 443 func TestAdditiveSuccess(t *testing.T) { 444 chain := newTestChain(t, []string{}) 445 job1 := newTestJob([]string{"a", "b"}) 446 job2 := newTestJob([]string{"a", "b", "c"}) 447 448 eg, ctx := errgroup.WithContext(context.Background()) 449 450 jdi1, err := chain.Start(job1) 451 require.NoError(t, err) 452 datums1 := superviseTestJob(ctx, eg, jdi1) 453 454 jdi2, err := chain.Start(job2) 455 require.NoError(t, err) 456 datums2 := superviseTestJob(ctx, eg, jdi2) 457 458 requireDatums(t, datums1, []string{"a", "b"}) 459 requireDatums(t, datums2, []string{"c"}) 460 requireChannelClosed(t, datums1) 461 requireChannelBlocked(t, datums2) 462 463 require.NoError(t, chain.Succeed(job1)) 464 requireChannelClosed(t, datums2) 465 466 require.NoError(t, chain.Succeed(job2)) 467 require.NoError(t, eg.Wait()) 468 469 requireChainEmpty(t, chain, []string{"a", "b", "c"}) 470 } 471 472 // Job 1: AB -> 1. Fail 473 // Job 2: ABC -> 2. Succeed 474 func TestAdditiveFail(t *testing.T) { 475 chain := newTestChain(t, []string{}) 476 job1 := newTestJob([]string{"a", "b"}) 477 job2 := newTestJob([]string{"a", "b", "c"}) 478 479 eg, ctx := errgroup.WithContext(context.Background()) 480 481 jdi1, err := chain.Start(job1) 482 require.NoError(t, err) 483 datums1 := superviseTestJob(ctx, eg, jdi1) 484 485 jdi2, err := chain.Start(job2) 486 require.NoError(t, err) 487 datums2 := superviseTestJob(ctx, eg, jdi2) 488 489 requireDatums(t, datums1, []string{"a", "b"}) 490 requireDatums(t, datums2, []string{"c"}) 491 requireChannelClosed(t, datums1) 492 requireChannelBlocked(t, datums2) 493 494 require.NoError(t, chain.Fail(job1)) 495 requireDatums(t, datums2, []string{"a", "b"}) 496 requireChannelClosed(t, datums2) 497 498 require.NoError(t, chain.Succeed(job2)) 499 require.NoError(t, eg.Wait()) 500 501 requireChainEmpty(t, chain, []string{"a", "b", "c"}) 502 } 503 504 // Job 1: AB -> 1. Succeed 505 // Job 2: BC -> 2. Succeed 506 // Job 3: BCD -> 3. Succeed 507 func TestCascadeSuccess(t *testing.T) { 508 chain := newTestChain(t, []string{}) 509 job1 := newTestJob([]string{"a", "b"}) 510 job2 := newTestJob([]string{"b", "c"}) 511 job3 := newTestJob([]string{"b", "c", "d"}) 512 513 eg, ctx := errgroup.WithContext(context.Background()) 514 515 jdi1, err := chain.Start(job1) 516 require.NoError(t, err) 517 datums1 := superviseTestJob(ctx, eg, jdi1) 518 519 jdi2, err := chain.Start(job2) 520 require.NoError(t, err) 521 datums2 := superviseTestJob(ctx, eg, jdi2) 522 523 jdi3, err := chain.Start(job3) 524 require.NoError(t, err) 525 datums3 := superviseTestJob(ctx, eg, jdi3) 526 527 requireDatums(t, datums1, []string{"a", "b"}) 528 requireDatums(t, datums2, []string{"c"}) 529 requireDatums(t, datums3, []string{"d"}) 530 requireChannelClosed(t, datums1) 531 requireChannelBlocked(t, datums2) 532 requireChannelBlocked(t, datums3) 533 534 require.NoError(t, chain.Succeed(job1)) 535 requireDatums(t, datums2, []string{"b"}) 536 requireChannelClosed(t, datums2) 537 requireChannelBlocked(t, datums3) 538 539 require.NoError(t, chain.Succeed(job2)) 540 requireChannelClosed(t, datums3) 541 542 require.NoError(t, chain.Succeed(job3)) 543 require.NoError(t, eg.Wait()) 544 545 requireChainEmpty(t, chain, []string{"b", "c", "d"}) 546 } 547 548 // Job 1: AB -> 1. Succeed 549 // Job 2: ABC -> 2. Fail 550 // Job 3: ABCD -> 3. Succeed 551 func TestCascadeFail(t *testing.T) { 552 chain := newTestChain(t, []string{}) 553 job1 := newTestJob([]string{"a", "b"}) 554 job2 := newTestJob([]string{"a", "b", "c"}) 555 job3 := newTestJob([]string{"a", "b", "c", "d"}) 556 557 eg, ctx := errgroup.WithContext(context.Background()) 558 559 jdi1, err := chain.Start(job1) 560 require.NoError(t, err) 561 datums1 := superviseTestJob(ctx, eg, jdi1) 562 563 jdi2, err := chain.Start(job2) 564 require.NoError(t, err) 565 datums2 := superviseTestJob(ctx, eg, jdi2) 566 567 jdi3, err := chain.Start(job3) 568 require.NoError(t, err) 569 datums3 := superviseTestJob(ctx, eg, jdi3) 570 571 requireDatums(t, datums1, []string{"a", "b"}) 572 requireDatums(t, datums2, []string{"c"}) 573 requireDatums(t, datums3, []string{"d"}) 574 requireChannelClosed(t, datums1) 575 requireChannelBlocked(t, datums2) 576 requireChannelBlocked(t, datums3) 577 578 require.NoError(t, chain.Succeed(job1)) 579 requireChannelClosed(t, datums2) 580 requireChannelBlocked(t, datums3) 581 582 require.NoError(t, chain.Fail(job2)) 583 requireDatums(t, datums3, []string{"c"}) 584 requireChannelClosed(t, datums3) 585 586 require.NoError(t, chain.Succeed(job3)) 587 require.NoError(t, eg.Wait()) 588 589 requireChainEmpty(t, chain, []string{"a", "b", "c", "d"}) 590 } 591 592 // Job 1: AB -> 2. Succeed 593 // Job 2: BC -> 1. Fail 594 // Job 3: BCD -> 3. Succeed 595 func TestSplitFail(t *testing.T) { 596 chain := newTestChain(t, []string{}) 597 job1 := newTestJob([]string{"a", "b"}) 598 job2 := newTestJob([]string{"b", "c"}) 599 job3 := newTestJob([]string{"b", "c", "d"}) 600 601 eg, ctx := errgroup.WithContext(context.Background()) 602 603 jdi1, err := chain.Start(job1) 604 require.NoError(t, err) 605 datums1 := superviseTestJob(ctx, eg, jdi1) 606 607 jdi2, err := chain.Start(job2) 608 require.NoError(t, err) 609 datums2 := superviseTestJobWithError(ctx, eg, jdi2, "job failed") 610 611 jdi3, err := chain.Start(job3) 612 require.NoError(t, err) 613 datums3 := superviseTestJob(ctx, eg, jdi3) 614 615 requireDatums(t, datums1, []string{"a", "b"}) 616 requireDatums(t, datums2, []string{"c"}) 617 requireDatums(t, datums3, []string{"d"}) 618 requireChannelClosed(t, datums1) 619 requireChannelBlocked(t, datums2) 620 requireChannelBlocked(t, datums3) 621 622 require.NoError(t, chain.Fail(job2)) 623 requireDatums(t, datums3, []string{"c"}) 624 //requireChannelClosed(t, datums2) 625 requireChannelBlocked(t, datums3) 626 627 require.NoError(t, chain.Succeed(job1)) 628 requireDatums(t, datums3, []string{"b"}) 629 requireChannelClosed(t, datums3) 630 631 require.NoError(t, chain.Succeed(job3)) 632 require.NoError(t, eg.Wait()) 633 634 requireChainEmpty(t, chain, []string{"b", "c", "d"}) 635 } 636 637 // Job 1: AB -> 1. Succeed (A and B recovered) 638 // Job 2: ABC -> 2. Succeed (A and C recovered) 639 // Job 3: ABCD -> 3. Succeed 640 func TestRecoveredDatums(t *testing.T) { 641 chain := newTestChain(t, []string{}) 642 job1 := newTestJob([]string{"a", "b"}) 643 job2 := newTestJob([]string{"a", "b", "c"}) 644 job3 := newTestJob([]string{"a", "b", "c", "d"}) 645 646 eg, ctx := errgroup.WithContext(context.Background()) 647 648 jdi1, err := chain.Start(job1) 649 require.NoError(t, err) 650 datums1 := superviseTestJob(ctx, eg, jdi1) 651 652 jdi2, err := chain.Start(job2) 653 require.NoError(t, err) 654 datums2 := superviseTestJob(ctx, eg, jdi2) 655 656 jdi3, err := chain.Start(job3) 657 require.NoError(t, err) 658 datums3 := superviseTestJob(ctx, eg, jdi3) 659 660 requireDatums(t, datums1, []string{"a", "b"}) 661 requireDatums(t, datums2, []string{"c"}) 662 requireDatums(t, datums3, []string{"d"}) 663 requireChannelClosed(t, datums1) 664 requireChannelBlocked(t, datums2) 665 requireChannelBlocked(t, datums3) 666 667 require.NoError(t, chain.RecoveredDatums(job1, datumsToSet([]string{"a", "b"}))) 668 require.NoError(t, chain.Succeed(job1)) 669 requireDatums(t, datums2, []string{"a", "b"}) 670 requireChannelClosed(t, datums2) 671 requireChannelBlocked(t, datums3) 672 673 require.NoError(t, chain.RecoveredDatums(job2, datumsToSet([]string{"a", "c"}))) 674 require.NoError(t, chain.Succeed(job2)) 675 requireDatums(t, datums3, []string{"a", "c"}) 676 requireChannelClosed(t, datums3) 677 678 require.NoError(t, chain.RecoveredDatums(job3, datumsToSet([]string{"c", "d"}))) 679 require.NoError(t, chain.Succeed(job3)) 680 require.NoError(t, eg.Wait()) 681 682 requireChainEmpty(t, chain, []string{"a", "b"}) 683 } 684 685 func TestEarlySuccess(t *testing.T) { 686 chain := newTestChain(t, []string{}) 687 job1 := newTestJob([]string{"a", "b"}) 688 689 _, err := chain.Start(job1) 690 require.NoError(t, err) 691 692 require.YesError(t, chain.Succeed(job1), "items remaining") 693 } 694 695 func TestEarlyFail(t *testing.T) { 696 chain := newTestChain(t, []string{"e", "f"}) 697 job := newTestJob([]string{"a", "b"}) 698 699 _, err := chain.Start(job) 700 require.NoError(t, err) 701 702 require.NoError(t, chain.Fail(job)) 703 requireChainEmpty(t, chain, []string{"e", "f"}) 704 } 705 706 func TestRepeatedDatumAdditiveSubtractiveOnBase(t *testing.T) { 707 jobDatums := []string{"c", "c", "b"} 708 chain := newTestChain(t, []string{"a", "b", "a", "b", "c"}) 709 job := newTestJob(jobDatums) 710 711 jdi, err := chain.Start(job) 712 require.NoError(t, err) 713 714 requireIteratorContents(t, jdi, jobDatums) 715 require.NoError(t, chain.Succeed(job)) 716 717 requireChainEmpty(t, chain, jobDatums) 718 } 719 720 func TestRepeatedDatumSubtractiveOnBase(t *testing.T) { 721 jobDatums := []string{"a", "a"} 722 chain := newTestChain(t, []string{"a", "b", "a", "b", "c"}) 723 job := newTestJob(jobDatums) 724 725 jdi, err := chain.Start(job) 726 require.NoError(t, err) 727 728 requireIteratorContents(t, jdi, jobDatums) 729 require.NoError(t, chain.Succeed(job)) 730 731 requireChainEmpty(t, chain, jobDatums) 732 } 733 734 func TestRepeatedDatumAdditiveOnBase(t *testing.T) { 735 baseDatums := []string{"a", "b", "a", "b", "c"} 736 newDatums := []string{"a", "c", "d"} 737 jobDatums := append([]string{}, baseDatums...) 738 jobDatums = append(jobDatums, newDatums...) 739 740 chain := newTestChain(t, baseDatums) 741 job := newTestJob(jobDatums) 742 743 jdi, err := chain.Start(job) 744 require.NoError(t, err) 745 746 requireIteratorContents(t, jdi, newDatums) 747 require.NoError(t, chain.Succeed(job)) 748 749 requireChainEmpty(t, chain, jobDatums) 750 } 751 752 func TestRepeatedDatumWithoutBase(t *testing.T) { 753 jobDatums := []string{"a", "b", "c", "a", "b", "a"} 754 chain := newTestChain(t, []string{}) 755 job := newTestJob(jobDatums) 756 757 jdi, err := chain.Start(job) 758 require.NoError(t, err) 759 760 requireIteratorContents(t, jdi, jobDatums) 761 require.NoError(t, chain.Succeed(job)) 762 763 requireChainEmpty(t, chain, jobDatums) 764 } 765 766 func TestNoSkipSuccess(t *testing.T) { 767 chain := NewNoSkipJobChain(&testHasher{}) 768 job1 := newTestJob([]string{"a", "b", "c", "d"}) 769 job2 := newTestJob([]string{"b", "c", "d", "e"}) 770 job3 := newTestJob([]string{"a", "f", "g"}) 771 job4 := newTestJob([]string{"h", "i"}) 772 773 eg, ctx := errgroup.WithContext(context.Background()) 774 775 jdi1, err := chain.Start(job1) 776 require.NoError(t, err) 777 datums1 := superviseTestJob(ctx, eg, jdi1) 778 779 jdi2, err := chain.Start(job2) 780 require.NoError(t, err) 781 datums2 := superviseTestJob(ctx, eg, jdi2) 782 783 jdi3, err := chain.Start(job3) 784 require.NoError(t, err) 785 datums3 := superviseTestJob(ctx, eg, jdi3) 786 787 jdi4, err := chain.Start(job4) 788 require.NoError(t, err) 789 datums4 := superviseTestJob(ctx, eg, jdi4) 790 791 requireDatums(t, datums1, []string{"a", "b", "c", "d"}) 792 requireDatums(t, datums2, []string{"b", "c", "d", "e"}) 793 requireDatums(t, datums3, []string{"a", "f", "g"}) 794 requireDatums(t, datums4, []string{"h", "i"}) 795 requireChannelClosed(t, datums1) 796 requireChannelClosed(t, datums2) 797 requireChannelClosed(t, datums3) 798 requireChannelClosed(t, datums4) 799 800 require.NoError(t, chain.Succeed(job1)) 801 require.NoError(t, chain.Succeed(job2)) 802 require.NoError(t, chain.Succeed(job3)) 803 require.NoError(t, chain.Succeed(job4)) 804 require.NoError(t, eg.Wait()) 805 }