corso/src/internal/observe/observe_test.go
Vaibhav Kamra 562c468a91
Progress bar improvements (#1302)
## Description

This adds the following functionality to the CLI progress output:
- Add a message e.g. to describe a completed stage
- Add a message describing a stage in progress (using a spinner) and trigger completion
- Add a progress tracker (as a counter) for a specified number of items (when the # items is known up front
- Improves `ItemProgress` by aligning the columns a bit better and also removing the progress bar and replacing
  with a byte counter

Finally - the above are used in the `backup` and `backup create onedrive` flows. Follow up PRs will wire these up for
exchange and the restore flows also.
 
## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* #1278 

## Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2022-10-24 19:52:48 +00:00

326 lines
7.2 KiB
Go

package observe_test
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/internal/tester"
)
type ObserveProgressUnitSuite struct {
suite.Suite
}
func TestObserveProgressUnitSuite(t *testing.T) {
suite.Run(t, new(ObserveProgressUnitSuite))
}
func (suite *ObserveProgressUnitSuite) TestItemProgress() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
observe.Complete()
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
from := make([]byte, 100)
prog, closer := observe.ItemProgress(
io.NopCloser(bytes.NewReader(from)),
"folder",
"test",
100)
require.NotNil(t, prog)
require.NotNil(t, closer)
defer closer()
var i int
for {
to := make([]byte, 25)
n, err := prog.Read(to)
if errors.Is(err, io.EOF) {
break
}
assert.NoError(t, 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() {
ctx, flush := tester.NewContext()
defer flush()
ctx, cancel := context.WithCancel(ctx)
t := suite.T()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
observe.Complete()
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
progCh, closer := observe.CollectionProgress("test", "testcat", "testertons")
require.NotNil(t, progCh)
require.NotNil(t, closer)
defer close(progCh)
for i := 0; i < 50; i++ {
progCh <- struct{}{}
}
go func() {
time.Sleep(1 * time.Second)
cancel()
}()
// blocks, but should resolve due to the ctx cancel
closer()
}
func (suite *ObserveProgressUnitSuite) TestCollectionProgress_unblockOnChannelClose() {
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
observe.Complete()
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
progCh, closer := observe.CollectionProgress("test", "testcat", "testertons")
require.NotNil(t, progCh)
require.NotNil(t, closer)
for i := 0; i < 50; i++ {
progCh <- struct{}{}
}
go func() {
time.Sleep(1 * time.Second)
close(progCh)
}()
// blocks, but should resolve due to the cancel
closer()
}
func (suite *ObserveProgressUnitSuite) TestObserveProgress() {
ctx, flush := tester.NewContext()
defer flush()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
message := "Test Message"
observe.Message(message)
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
}
func (suite *ObserveProgressUnitSuite) TestObserveProgressWithCompletion() {
ctx, flush := tester.NewContext()
defer flush()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
message := "Test Message"
ch, closer := observe.MessageWithCompletion(message)
// Trigger completion
ch <- struct{}{}
// Run the closer - this should complete because the bar was compelted above
closer()
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
require.Contains(suite.T(), recorder.String(), "done")
}
func (suite *ObserveProgressUnitSuite) TestObserveProgressWithChannelClosed() {
ctx, flush := tester.NewContext()
defer flush()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
message := "Test Message"
ch, closer := observe.MessageWithCompletion(message)
// Close channel without completing
close(ch)
// Run the closer - this should complete because the channel was closed above
closer()
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
require.Contains(suite.T(), recorder.String(), "done")
}
func (suite *ObserveProgressUnitSuite) TestObserveProgressWithContextCancelled() {
ctx, flush := tester.NewContext()
defer flush()
ctx, cancel := context.WithCancel(ctx)
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
message := "Test Message"
_, closer := observe.MessageWithCompletion(message)
// cancel context
cancel()
// Run the closer - this should complete because the context was closed above
closer()
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
}
func (suite *ObserveProgressUnitSuite) TestObserveProgressWithCount() {
ctx, flush := tester.NewContext()
defer flush()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
header := "Header"
message := "Test Message"
count := 3
ch, closer := observe.ProgressWithCount(header, message, int64(count))
for i := 0; i < count; i++ {
ch <- struct{}{}
}
// Run the closer - this should complete because the context was closed above
closer()
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
require.Contains(suite.T(), recorder.String(), fmt.Sprintf("%d/%d", count, count))
}
func (suite *ObserveProgressUnitSuite) TestObserveProgressWithCountChannelClosed() {
ctx, flush := tester.NewContext()
defer flush()
recorder := strings.Builder{}
observe.SeedWriter(ctx, &recorder, false)
defer func() {
// don't cross-contaminate other tests.
//nolint:forbidigo
observe.SeedWriter(context.Background(), nil, false)
}()
header := "Header"
message := "Test Message"
count := 3
ch, closer := observe.ProgressWithCount(header, message, int64(count))
close(ch)
// Run the closer - this should complete because the context was closed above
closer()
observe.Complete()
require.NotEmpty(suite.T(), recorder.String())
require.Contains(suite.T(), recorder.String(), message)
require.Contains(suite.T(), recorder.String(), fmt.Sprintf("%d/%d", 0, count))
}