package observe import ( "bytes" "context" "errors" "fmt" "io" "strings" "testing" "time" "github.com/alcionai/clues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/tester" ) type ObserveProgressUnitSuite struct { tester.Suite } func TestObserveProgressUnitSuite(t *testing.T) { suite.Run(t, &ObserveProgressUnitSuite{ Suite: tester.NewUnitSuite(t), }) } var ( tst = "test" testcat = "testcat" testertons = "testertons" ) func (suite *ObserveProgressUnitSuite) TestItemProgress() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. Complete() //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() from := make([]byte, 100) prog, abort := ItemProgress( ctx, io.NopCloser(bytes.NewReader(from)), "folder", tst, 100) require.NotNil(t, prog) require.NotNil(t, abort) var i int for { to := make([]byte, 25) n, err := prog.Read(to) if errors.Is(err, io.EOF) { break } assert.NoError(t, err, clues.ToCore(err)) assert.Equal(t, 25, n) i++ } // mpb doesn't transmit any written values to the output writer until // bar completion. Since we clean up after the bars, the recorder // traces nothing. // recorded := recorder.String() // assert.Contains(t, recorded, "25%") // assert.Contains(t, recorded, "50%") // assert.Contains(t, recorded, "75%") assert.Equal(t, 4, i) } func (suite *ObserveProgressUnitSuite) TestCollectionProgress_unblockOnCtxCancel() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() ctx, cancel := context.WithCancel(ctx) recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. Complete() //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() progCh := CollectionProgress(ctx, testcat, testertons) require.NotNil(t, progCh) defer close(progCh) for i := 0; i < 50; i++ { progCh <- struct{}{} } go func() { time.Sleep(1 * time.Second) cancel() }() } func (suite *ObserveProgressUnitSuite) TestCollectionProgress_unblockOnChannelClose() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. Complete() //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() progCh := CollectionProgress(ctx, testcat, testertons) require.NotNil(t, progCh) for i := 0; i < 50; i++ { progCh <- struct{}{} } go func() { time.Sleep(1 * time.Second) close(progCh) }() } func (suite *ObserveProgressUnitSuite) TestObserveProgress() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() message := "Test Message" Message(ctx, message) Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) } func (suite *ObserveProgressUnitSuite) TestObserveProgressWithCompletion() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() message := "Test Message" ch := MessageWithCompletion(ctx, message) // Trigger completion ch <- struct{}{} Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) require.Contains(t, recorder.String(), "done") } func (suite *ObserveProgressUnitSuite) TestObserveProgressWithChannelClosed() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() message := "Test Message" ch := MessageWithCompletion(ctx, message) // Close channel without completing close(ch) Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) require.Contains(t, recorder.String(), "done") } func (suite *ObserveProgressUnitSuite) TestObserveProgressWithContextCancelled() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() ctx, cancel := context.WithCancel(ctx) recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() message := "Test Message" _ = MessageWithCompletion(ctx, message) // cancel context cancel() Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) } func (suite *ObserveProgressUnitSuite) TestObserveProgressWithCount() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() header := "Header" message := "Test Message" count := 3 ch := ProgressWithCount(ctx, header, message, int64(count)) for i := 0; i < count; i++ { ch <- struct{}{} } Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) require.Contains(t, recorder.String(), fmt.Sprintf("%d/%d", count, count)) } func (suite *ObserveProgressUnitSuite) TestrogressWithCountChannelClosed() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() recorder := strings.Builder{} SeedWriter(ctx, &recorder, nil) defer func() { // don't cross-contaminate other tests. //nolint:forbidigo SeedWriter(context.Background(), nil, nil) }() header := "Header" message := "Test Message" count := 3 ch := ProgressWithCount(ctx, header, message, int64(count)) close(ch) Complete() require.NotEmpty(t, recorder.String()) require.Contains(t, recorder.String(), message) require.Contains(t, recorder.String(), fmt.Sprintf("%d/%d", 0, count)) } func (suite *ObserveProgressUnitSuite) TestListen() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() var ( ch = make(chan struct{}) end bool onEnd = func() { end = true } inc bool onInc = func() { inc = true } ) go func() { time.Sleep(500 * time.Millisecond) ch <- struct{}{} time.Sleep(500 * time.Millisecond) close(ch) }() // regular channel close listen(ctx, ch, onEnd, onInc) assert.True(t, end) assert.True(t, inc) } func (suite *ObserveProgressUnitSuite) TestListen_close() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() var ( ch = make(chan struct{}) end bool onEnd = func() { end = true } inc bool onInc = func() { inc = true } ) go func() { time.Sleep(500 * time.Millisecond) close(ch) }() // regular channel close listen(ctx, ch, onEnd, onInc) assert.True(t, end) assert.False(t, inc) } func (suite *ObserveProgressUnitSuite) TestListen_cancel() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() ctx, cancelFn := context.WithCancel(ctx) var ( ch = make(chan struct{}) end bool onEnd = func() { end = true } inc bool onInc = func() { inc = true } ) go func() { time.Sleep(500 * time.Millisecond) cancelFn() }() // regular channel close listen(ctx, ch, onEnd, onInc) assert.True(t, end) assert.False(t, inc) }