## 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
326 lines
7.2 KiB
Go
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))
|
|
}
|