diff --git a/_examples/callback/callback_test.go b/_examples/callback/callback_test.go index 3c58a0d..495b9c1 100644 --- a/_examples/callback/callback_test.go +++ b/_examples/callback/callback_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" "github.com/luno/workflow" "github.com/luno/workflow/adapters/memrecordstore" "github.com/luno/workflow/adapters/memrolescheduler" "github.com/luno/workflow/adapters/memstreamer" + "github.com/stretchr/testify/require" "github.com/luno/workflow/_examples/callback" ) @@ -26,7 +26,7 @@ func TestCallbackWorkflow(t *testing.T) { foreignID := "andrew" runID, err := wf.Trigger(ctx, foreignID, callback.StatusStarted) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.TriggerCallbackOn(t, wf, foreignID, runID, callback.StatusStarted, callback.EmailConfirmationResponse{ Confirmed: true, diff --git a/_examples/gettingstarted/gettingstarted_test.go b/_examples/gettingstarted/gettingstarted_test.go index 8d9e572..d33e240 100644 --- a/_examples/gettingstarted/gettingstarted_test.go +++ b/_examples/gettingstarted/gettingstarted_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" "github.com/luno/workflow" "github.com/luno/workflow/adapters/memrecordstore" "github.com/luno/workflow/adapters/memrolescheduler" "github.com/luno/workflow/adapters/memstreamer" "github.com/luno/workflow/adapters/memtimeoutstore" + "github.com/stretchr/testify/require" "github.com/luno/workflow/_examples/gettingstarted" ) @@ -28,7 +28,7 @@ func TestWorkflow(t *testing.T) { foreignID := "82347982374982374" _, err := wf.Trigger(ctx, foreignID, gettingstarted.StatusStarted) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.Require(t, wf, foreignID, gettingstarted.StatusReadTheDocs, gettingstarted.GettingStarted{ ReadTheDocs: "✅", diff --git a/_examples/schedule/schedule_test.go b/_examples/schedule/schedule_test.go index 36330de..2b40f22 100644 --- a/_examples/schedule/schedule_test.go +++ b/_examples/schedule/schedule_test.go @@ -2,10 +2,10 @@ package schedule_test import ( "context" + "errors" "testing" "time" - "github.com/luno/jettison/jtest" "github.com/luno/workflow" "github.com/luno/workflow/adapters/memrecordstore" "github.com/luno/workflow/adapters/memrolescheduler" @@ -37,7 +37,7 @@ func TestExampleWorkflow(t *testing.T) { go func() { err := wf.Schedule(foreignID, schedule.StatusStarted, "@hourly") - jtest.RequireNil(t, err) + require.Nil(t, err) }() // Give time for go routine to spin up @@ -45,7 +45,7 @@ func TestExampleWorkflow(t *testing.T) { _, err := recordStore.Latest(ctx, wf.Name, foreignID) // Expect there to be no entries yet - jtest.Require(t, workflow.ErrRecordNotFound, err) + require.True(t, errors.Is(err, workflow.ErrRecordNotFound)) clock.Step(time.Hour) @@ -53,7 +53,7 @@ func TestExampleWorkflow(t *testing.T) { time.Sleep(200 * time.Millisecond) firstScheduled, err := recordStore.Latest(ctx, wf.Name, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, "schedule trigger example", firstScheduled.WorkflowName) require.Equal(t, "hourly-run", firstScheduled.ForeignID) @@ -64,7 +64,7 @@ func TestExampleWorkflow(t *testing.T) { time.Sleep(200 * time.Millisecond) secondScheduled, err := recordStore.Latest(ctx, wf.Name, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, "schedule trigger example", secondScheduled.WorkflowName) require.Equal(t, "hourly-run", secondScheduled.ForeignID) diff --git a/_examples/timeout/timeout_test.go b/_examples/timeout/timeout_test.go index be32281..5b7ec72 100644 --- a/_examples/timeout/timeout_test.go +++ b/_examples/timeout/timeout_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/luno/workflow" "github.com/luno/workflow/adapters/memrecordstore" "github.com/luno/workflow/adapters/memrolescheduler" "github.com/luno/workflow/adapters/memstreamer" "github.com/luno/workflow/adapters/memtimeoutstore" + "github.com/stretchr/testify/require" clocktesting "k8s.io/utils/clock/testing" "github.com/luno/workflow/_examples/timeout" @@ -33,7 +33,7 @@ func TestTimeoutWorkflow(t *testing.T) { foreignID := "andrew" runID, err := wf.Trigger(ctx, foreignID, timeout.StatusStarted) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.AwaitTimeoutInsert(t, wf, foreignID, runID, timeout.StatusStarted) diff --git a/adapters/adaptertest/eventstreaming.go b/adapters/adaptertest/eventstreaming.go index 098ac92..82c22ae 100644 --- a/adapters/adaptertest/eventstreaming.go +++ b/adapters/adaptertest/eventstreaming.go @@ -6,7 +6,6 @@ import ( "time" "github.com/google/uuid" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -91,14 +90,14 @@ func RunEventStreamerTest(t *testing.T, constructor workflow.EventStreamer) { CountryCode: "GB", } runId, err := wf.Trigger(ctx, foreignID, SyncStatusStarted, workflow.WithInitialValue[User, SyncStatus](&u)) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.AwaitTimeoutInsert(t, wf, foreignID, runId, SyncStatusEmailSet) clock.Step(time.Hour) record, err := wf.Await(ctx, foreignID, runId, SyncStatusCompleted) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, "andrew@workflow.com", record.Object.Email) require.Equal(t, SyncStatusCompleted.String(), record.Status.String()) diff --git a/adapters/adaptertest/recordstore.go b/adapters/adaptertest/recordstore.go index bc15dff..474e08c 100644 --- a/adapters/adaptertest/recordstore.go +++ b/adapters/adaptertest/recordstore.go @@ -8,7 +8,6 @@ import ( "time" "github.com/google/uuid" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -39,20 +38,20 @@ func testLatest(t *testing.T, factory func() workflow.RecordStore) { maker := func(recordID int64) (workflow.OutboxEventData, error) { return workflow.OutboxEventData{}, nil } err := store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) latest, err := store.Latest(ctx, expected.WorkflowName, expected.ForeignID) - jtest.RequireNil(t, err) + require.Nil(t, err) recordIsEqual(t, *expected, *latest) expected.Status = int(statusEnd) expected.RunState = workflow.RunStateCompleted err = store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) latest, err = store.Latest(ctx, expected.WorkflowName, expected.ForeignID) - jtest.RequireNil(t, err) + require.Nil(t, err) recordIsEqual(t, *expected, *latest) }) } @@ -65,10 +64,10 @@ func testLookup(t *testing.T, factory func() workflow.RecordStore) { maker := func(recordID int64) (workflow.OutboxEventData, error) { return workflow.OutboxEventData{}, nil } err := store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) latest, err := store.Lookup(ctx, 1) - jtest.RequireNil(t, err) + require.Nil(t, err) recordIsEqual(t, *expected, *latest) }) @@ -82,27 +81,27 @@ func testStore(t *testing.T, factory func() workflow.RecordStore) { maker := func(recordID int64) (workflow.OutboxEventData, error) { return workflow.OutboxEventData{}, nil } err := store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) latest, err := store.Lookup(ctx, 1) - jtest.RequireNil(t, err) + require.Nil(t, err) latest.Status = int(statusMiddle) expected.Status = int(statusMiddle) err = store.Store(ctx, latest, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) recordIsEqual(t, *expected, *latest) latest, err = store.Lookup(ctx, 1) - jtest.RequireNil(t, err) + require.Nil(t, err) latest.Status = int(statusEnd) expected.Status = int(statusEnd) err = store.Store(ctx, latest, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) recordIsEqual(t, *expected, *latest) }) @@ -121,10 +120,10 @@ func testListOutboxEvents(t *testing.T, factory func() workflow.RecordStore) { } err := store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) ls, err := store.ListOutboxEvents(ctx, expected.WorkflowName, 1000) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 1, len(ls)) @@ -133,7 +132,7 @@ func testListOutboxEvents(t *testing.T, factory func() workflow.RecordStore) { var r outboxpb.OutboxRecord err = proto.Unmarshal(ls[0].Data, &r) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, int32(statusStarted), r.Type) require.Equal(t, "my_workflow-1", r.Headers[string(workflow.HeaderTopic)]) @@ -155,21 +154,21 @@ func testDeleteOutboxEvent(t *testing.T, factory func() workflow.RecordStore) { } err := store.Store(ctx, expected, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) latest, err := store.Lookup(ctx, 1) - jtest.RequireNil(t, err) + require.Nil(t, err) latest.Status = int(statusMiddle) ls, err := store.ListOutboxEvents(ctx, expected.WorkflowName, 1000) - jtest.RequireNil(t, err) + require.Nil(t, err) err = store.DeleteOutboxEvent(ctx, ls[0].ID) - jtest.RequireNil(t, err) + require.Nil(t, err) ls, err = store.ListOutboxEvents(ctx, expected.WorkflowName, 1000) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 0, len(ls)) }) @@ -186,22 +185,22 @@ func testList(t *testing.T, factory func() workflow.RecordStore) { seedCount := 1000 for i := 0; i < seedCount; i++ { err := store.Store(ctx, dummyWireRecord(t), maker) - jtest.RequireNil(t, err) + require.Nil(t, err) } ls, err := store.List(ctx, workflowName, 0, 53, workflow.OrderTypeAscending) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 53, len(ls)) ls2, err := store.List(ctx, workflowName, 53, 100, workflow.OrderTypeAscending) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 100, len(ls2)) // Make sure the last of the first page is not the same as the first of the next page require.NotEqual(t, ls[52].ID, ls2[0]) ls3, err := store.List(ctx, workflowName, 153, seedCount-153, workflow.OrderTypeAscending) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, seedCount-153, len(ls3)) // Make sure the last of the first page is not the same as the first of the next page @@ -209,12 +208,12 @@ func testList(t *testing.T, factory func() workflow.RecordStore) { // Make sure that if 950 is the offset and we only have 1000 then only 1 item would be returned lastPageAsc, err := store.List(ctx, workflowName, 950, 1000, workflow.OrderTypeAscending) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 50, len(lastPageAsc)) require.Equal(t, int64(1000), lastPageAsc[len(lastPageAsc)-1].ID) lastPageDesc, err := store.List(ctx, workflowName, 950, 1000, workflow.OrderTypeDescending) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 50, len(lastPageDesc)) require.Equal(t, int64(1000), lastPageDesc[0].ID) }) @@ -229,20 +228,20 @@ func testList(t *testing.T, factory func() workflow.RecordStore) { wr.ForeignID = foreignID err := store.Store(ctx, wr, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) } } ls, err := store.List(ctx, workflowName, 0, 100, workflow.OrderTypeAscending, workflow.FilterByForeignID(foreignIDs[0])) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 20, len(ls)) ls2, err := store.List(ctx, workflowName, 0, 100, workflow.OrderTypeAscending, workflow.FilterByForeignID(foreignIDs[1])) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 20, len(ls2)) ls3, err := store.List(ctx, workflowName, 0, 100, workflow.OrderTypeAscending, workflow.FilterByForeignID("random")) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, 0, len(ls3)) }) @@ -263,13 +262,13 @@ func testList(t *testing.T, factory func() workflow.RecordStore) { wr.RunState = runState err := store.Store(ctx, wr, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) } } for runState, count := range config { ls, err := store.List(ctx, workflowName, 0, 100, workflow.OrderTypeAscending, workflow.FilterByRunState(runState)) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, count, len(ls), fmt.Sprintf("Expected to have %v entries of %v", count, runState.String())) for _, l := range ls { @@ -292,13 +291,13 @@ func testList(t *testing.T, factory func() workflow.RecordStore) { newRecord.Status = int(status) err := store.Store(ctx, newRecord, maker) - jtest.RequireNil(t, err) + require.Nil(t, err) } } for status, count := range config { ls, err := store.List(ctx, workflowName, 0, 100, workflow.OrderTypeAscending, workflow.FilterByStatus(int64(status))) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, count, len(ls)) for _, l := range ls { @@ -316,7 +315,7 @@ func dummyWireRecordWithID(t *testing.T, id int64) *workflow.Record { workflowName := "my_workflow" foreignID := "Andrew Wormald" runID, err := uuid.NewUUID() - jtest.RequireNil(t, err) + require.Nil(t, err) type example struct { name string @@ -324,7 +323,7 @@ func dummyWireRecordWithID(t *testing.T, id int64) *workflow.Record { e := example{name: foreignID} b, err := json.Marshal(e) - jtest.RequireNil(t, err) + require.Nil(t, err) createdAt := time.Now() diff --git a/adapters/adaptertest/rolescheduler.go b/adapters/adaptertest/rolescheduler.go index 73df61a..98c357b 100644 --- a/adapters/adaptertest/rolescheduler.go +++ b/adapters/adaptertest/rolescheduler.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -30,7 +29,7 @@ func testReturnedContext(t *testing.T, factory func() workflow.RoleScheduler) { ctxWithValue := context.WithValue(ctx, "parent", "context") ctx2, cancel, err := rs.Await(ctxWithValue, "leader") - jtest.RequireNil(t, err) + require.Nil(t, err) t.Cleanup(cancel) @@ -45,14 +44,14 @@ func testLocking(t *testing.T, factory func() workflow.RoleScheduler) { ctxWithValue := context.WithValue(ctx, "parent", "context") ctx2, cancel, err := rs.Await(ctxWithValue, "leader") - jtest.RequireNil(t, err) + require.Nil(t, err) t.Cleanup(cancel) roleReleased := make(chan bool, 1) go func(done chan bool) { _, _, err := rs.Await(ctxWithValue, "leader") - jtest.RequireNil(t, err) + require.Nil(t, err) roleReleased <- true @@ -78,7 +77,7 @@ func testReleasing(t *testing.T, factory func() workflow.RoleScheduler) { ctx := context.Background() _, cancel, err := rs.Await(ctx, "leader") - jtest.RequireNil(t, err) + require.Nil(t, err) t.Cleanup(cancel) @@ -88,7 +87,7 @@ func testReleasing(t *testing.T, factory func() workflow.RoleScheduler) { roleReleased := make(chan bool, 1) go func(ctx context.Context, done chan bool) { _, _, err := rs.Await(ctx2, "leader") - jtest.RequireNil(t, err) + require.Nil(t, err) roleReleased <- true }(ctx2, roleReleased) diff --git a/adapters/adaptertest/timeoutstore.go b/adapters/adaptertest/timeoutstore.go index ef6b0ba..55a4019 100644 --- a/adapters/adaptertest/timeoutstore.go +++ b/adapters/adaptertest/timeoutstore.go @@ -2,10 +2,10 @@ package adaptertest import ( "context" + "fmt" "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -13,8 +13,7 @@ import ( func RunTimeoutStoreTest(t *testing.T, factory func() workflow.TimeoutStore) { tests := []func(t *testing.T, factory func() workflow.TimeoutStore){ - testCancelTimeout, - testCompleteTimeout, + testCompleteAndCancelTimeout, testListTimeout, } @@ -23,145 +22,59 @@ func RunTimeoutStoreTest(t *testing.T, factory func() workflow.TimeoutStore) { } } -func testCancelTimeout(t *testing.T, factory func() workflow.TimeoutStore) { - store := factory() - ctx := context.Background() +func testCompleteAndCancelTimeout(t *testing.T, factory func() workflow.TimeoutStore) { + t.Run("Complete and Cancel timeouts", func(t *testing.T) { + store := factory() + ctx := context.Background() - err := store.Create(ctx, "example", "andrew", "1", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - err = store.Create(ctx, "example", "andrew", "2", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - err = store.Create(ctx, "example", "andrew", "3", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - timeout, err := store.ListValid(ctx, "example", int(statusStarted), time.Now()) - jtest.RequireNil(t, err) - - require.Equal(t, 3, len(timeout)) - - require.Equal(t, "example", timeout[0].WorkflowName) - require.Equal(t, "andrew", timeout[0].ForeignID) - require.Equal(t, "1", timeout[0].RunID) - require.False(t, timeout[0].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[0].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[0].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[1].WorkflowName) - require.Equal(t, "andrew", timeout[1].ForeignID) - require.Equal(t, "2", timeout[1].RunID) - require.False(t, timeout[1].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[1].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[1].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[2].WorkflowName) - require.Equal(t, "andrew", timeout[2].ForeignID) - require.Equal(t, "3", timeout[2].RunID) - require.False(t, timeout[2].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[2].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[2].CreatedAt, time.Second) - - err = store.Cancel(ctx, 2) - jtest.RequireNil(t, err) - - timeout, err = store.ListValid(ctx, "example", int(statusStarted), time.Now()) - jtest.RequireNil(t, err) - - require.Equal(t, 2, len(timeout)) - - require.Equal(t, "example", timeout[0].WorkflowName) - require.Equal(t, "andrew", timeout[0].ForeignID) - require.Equal(t, "1", timeout[0].RunID) - require.False(t, timeout[0].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[0].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[0].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[1].WorkflowName) - require.Equal(t, "andrew", timeout[1].ForeignID) - require.Equal(t, "3", timeout[1].RunID) - require.False(t, timeout[1].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[1].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[1].CreatedAt, time.Second) -} + seed(t, store, 3) -func testCompleteTimeout(t *testing.T, factory func() workflow.TimeoutStore) { - store := factory() - ctx := context.Background() + err := store.Complete(ctx, 2) + require.Nil(t, err) - err := store.Create(ctx, "example", "andrew", "1", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - err = store.Create(ctx, "example", "andrew", "2", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - err = store.Create(ctx, "example", "andrew", "3", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) - - timeout, err := store.ListValid(ctx, "example", int(statusStarted), time.Now()) - jtest.RequireNil(t, err) - - require.Equal(t, 3, len(timeout)) - - require.Equal(t, "example", timeout[0].WorkflowName) - require.Equal(t, "andrew", timeout[0].ForeignID) - require.Equal(t, "1", timeout[0].RunID) - require.False(t, timeout[0].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[0].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[0].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[1].WorkflowName) - require.Equal(t, "andrew", timeout[1].ForeignID) - require.Equal(t, "2", timeout[1].RunID) - require.False(t, timeout[1].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[1].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[1].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[2].WorkflowName) - require.Equal(t, "andrew", timeout[2].ForeignID) - require.Equal(t, "3", timeout[2].RunID) - require.False(t, timeout[2].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[2].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[2].CreatedAt, time.Second) - - err = store.Complete(ctx, 2) - jtest.RequireNil(t, err) - - timeout, err = store.ListValid(ctx, "example", int(statusStarted), time.Now()) - jtest.RequireNil(t, err) - - require.Equal(t, 2, len(timeout)) - - require.Equal(t, "example", timeout[0].WorkflowName) - require.Equal(t, "andrew", timeout[0].ForeignID) - require.Equal(t, "1", timeout[0].RunID) - require.False(t, timeout[0].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[0].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[0].CreatedAt, time.Second) - - require.Equal(t, "example", timeout[1].WorkflowName) - require.Equal(t, "andrew", timeout[1].ForeignID) - require.Equal(t, "3", timeout[1].RunID) - require.False(t, timeout[1].Completed) - require.WithinDuration(t, time.Now().Add(-time.Hour), timeout[1].ExpireAt, time.Second) - require.WithinDuration(t, time.Now(), timeout[1].CreatedAt, time.Second) + err = store.Cancel(ctx, 3) + require.Nil(t, err) + + timeouts, err := store.ListValid(ctx, "example", int(statusStarted), time.Now()) + require.Nil(t, err) + + expect(t, 1, timeouts) + }) } -func testListTimeout(t *testing.T, factory func() workflow.TimeoutStore) { - store := factory() +func seed(t *testing.T, store workflow.TimeoutStore, count int) { ctx := context.Background() + for i := range count { + err := store.Create(ctx, "example", "andrew", fmt.Sprintf("%v", i+1), int(statusStarted), time.Now().Add(-time.Hour)) + require.Nil(t, err) + } +} - err := store.Create(ctx, "example", "andrew", "1", int(statusStarted), time.Now().Add(-time.Hour)) - jtest.RequireNil(t, err) +func expect(t *testing.T, count int, actual []workflow.TimeoutRecord) { + // Assert the length + require.Equal(t, count, len(actual)) + + // Validate the contents + for i, timeout := range actual { + require.Equal(t, "example", timeout.WorkflowName) + require.Equal(t, "andrew", timeout.ForeignID) + require.Equal(t, fmt.Sprintf("%v", i+1), timeout.RunID) + require.False(t, timeout.Completed) + require.WithinDuration(t, time.Now().Add(-time.Hour), timeout.ExpireAt, time.Second) + require.WithinDuration(t, time.Now(), timeout.CreatedAt, time.Second) + } +} - err = store.Create(ctx, "example", "andrew", "2", int(statusMiddle), time.Now().Add(time.Hour)) - jtest.RequireNil(t, err) +func testListTimeout(t *testing.T, factory func() workflow.TimeoutStore) { + t.Run("List timeouts", func(t *testing.T) { + store := factory() + ctx := context.Background() - err = store.Create(ctx, "example", "andrew", "3", int(statusEnd), time.Now().Add(time.Hour*2)) - jtest.RequireNil(t, err) + seed(t, store, 3) - timeout, err := store.List(ctx, "example") - jtest.RequireNil(t, err) + timeouts, err := store.List(ctx, "example") + require.Nil(t, err) - require.Equal(t, 3, len(timeout)) + expect(t, 3, timeouts) + }) } diff --git a/adapters/jlog/go.mod b/adapters/jlog/go.mod new file mode 100644 index 0000000..48b7e45 --- /dev/null +++ b/adapters/jlog/go.mod @@ -0,0 +1,33 @@ +module github.com/luno/workflow/adapters/jlog + +go 1.22.3 + +replace github.com/luno/workflow => ../.. + +require ( + github.com/luno/jettison v0.0.0-20240722160230-b42bd507a5f6 + github.com/luno/workflow v0.1.2 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.15.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/robfig/cron/v3 v3.0.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect +) diff --git a/adapters/jlog/go.sum b/adapters/jlog/go.sum new file mode 100644 index 0000000..82666bd --- /dev/null +++ b/adapters/jlog/go.sum @@ -0,0 +1,60 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/luno/jettison v0.0.0-20240722160230-b42bd507a5f6 h1:0s90//MXlAOvp91eNGIoCHf1X0jQ+TcPRlWSuuPREW4= +github.com/luno/jettison v0.0.0-20240722160230-b42bd507a5f6/go.mod h1:cV8KOstEDY+Su4dcN1dadoXC7xmyEqtXAw6Nywia/z8= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= +github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= +github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= +github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= +k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/adapters/jlog/jlog.go b/adapters/jlog/jlog.go new file mode 100644 index 0000000..fa68b65 --- /dev/null +++ b/adapters/jlog/jlog.go @@ -0,0 +1,26 @@ +package jlog + +import ( + "context" + + "github.com/luno/jettison/errors" + "github.com/luno/jettison/j" + "github.com/luno/jettison/log" + "github.com/luno/workflow" +) + +func New() *logger { + return &logger{} +} + +type logger struct{} + +func (l logger) Debug(ctx context.Context, msg string, meta map[string]string) { + log.Debug(ctx, msg, j.MKS(meta)) +} + +func (l logger) Error(ctx context.Context, err error) { + log.Error(ctx, errors.Wrap(err, "")) +} + +var _ workflow.Logger = (*logger)(nil) diff --git a/adapters/jlog/jlog_test.go b/adapters/jlog/jlog_test.go new file mode 100644 index 0000000..225c546 --- /dev/null +++ b/adapters/jlog/jlog_test.go @@ -0,0 +1,44 @@ +package jlog_test + +import ( + "bytes" + "context" + "testing" + + "github.com/luno/jettison/errors" + "github.com/luno/jettison/log" + "github.com/stretchr/testify/require" + + "github.com/luno/workflow/adapters/jlog" +) + +func TestDebug(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + jLogger := log.NewCmdLogger(buf, true) + log.SetLoggerForTesting(t, jLogger) + + logger := jlog.New() + ctx := context.Background() + logger.Debug(ctx, "test message", map[string]string{"testKey": "testValue"}) + + require.Equal(t, "D 00:00:00.000 g/l/w/a/jlog/jlog.go:19: test message[testkey=testValue]\n", buf.String()) +} + +func TestError(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + jLogger := log.NewCmdLogger(buf, true) + log.SetLoggerForTesting(t, jLogger) + + logger := jlog.New() + ctx := context.Background() + testErr := errors.New("test error") + logger.Error(ctx, testErr) + + expected := `E 00:00:00.000 g/l/w/a/jlog/jlog.go:23: error(s) + test error + - github.com/luno/workflow/adapters/jlog/jlog_test.go:34 TestError + - testing/testing.go:1689 tRunner + - runtime/asm_arm64.s:1222 goexit +` + require.Equal(t, expected, buf.String()) +} diff --git a/adapters/kafkastreamer/kafka.go b/adapters/kafkastreamer/kafka.go index 958ed3f..c96820f 100644 --- a/adapters/kafkastreamer/kafka.go +++ b/adapters/kafkastreamer/kafka.go @@ -2,13 +2,12 @@ package kafkastreamer import ( "context" + "errors" "strconv" "time" - "github.com/luno/jettison/errors" - "github.com/segmentio/kafka-go" - "github.com/luno/workflow" + "github.com/segmentio/kafka-go" ) func New(brokers []string) *StreamConstructor { diff --git a/adapters/kafkastreamer/kafka_test.go b/adapters/kafkastreamer/kafka_test.go index bc696ce..a8e8bb5 100644 --- a/adapters/kafkastreamer/kafka_test.go +++ b/adapters/kafkastreamer/kafka_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/luno/workflow" "github.com/luno/workflow/adapters/adaptertest" "github.com/segmentio/kafka-go" + "github.com/stretchr/testify/require" "github.com/luno/workflow/adapters/kafkastreamer" ) @@ -58,7 +58,7 @@ func TestConnector(t *testing.T) { Key: []byte(e.ForeignID), } err := writer.WriteMessages(ctx, m) - jtest.RequireNil(t, err) + require.Nil(t, err) } return constructor diff --git a/adapters/memrecordstore/memrecordstore.go b/adapters/memrecordstore/memrecordstore.go index f5c13b8..fdea968 100644 --- a/adapters/memrecordstore/memrecordstore.go +++ b/adapters/memrecordstore/memrecordstore.go @@ -2,11 +2,9 @@ package memrecordstore import ( "context" - "fmt" "strconv" "sync" - "github.com/luno/jettison/errors" "k8s.io/utils/clock" "github.com/luno/workflow" @@ -70,7 +68,7 @@ func (s *Store) Lookup(ctx context.Context, id int64) (*workflow.Record, error) record, ok := s.store[id] if !ok { - return nil, errors.Wrap(workflow.ErrRecordNotFound, "") + return nil, workflow.ErrRecordNotFound } // Return a new pointer so modifications don't affect the store. @@ -128,7 +126,7 @@ func (s *Store) Latest(ctx context.Context, workflowName, foreignID string) (*wo uk := uniqueKey(workflowName, foreignID) record, ok := s.keyIndex[uk] if !ok { - return nil, errors.Wrap(workflow.ErrRecordNotFound, "") + return nil, workflow.ErrRecordNotFound } // Return a new pointer so modifications don't affect the store. @@ -227,7 +225,6 @@ func (s *Store) List(ctx context.Context, workflowName string, offsetID int64, l entry, ok := filteredStore[i] if !ok { - fmt.Println("cannot find ", i) continue } diff --git a/adapters/memstreamer/connector.go b/adapters/memstreamer/connector.go index 6d489d7..212751b 100644 --- a/adapters/memstreamer/connector.go +++ b/adapters/memstreamer/connector.go @@ -2,11 +2,10 @@ package memstreamer import ( "context" + "errors" "sync" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" "k8s.io/utils/clock" "github.com/luno/workflow" @@ -78,7 +77,7 @@ func (c *consumer) Recv(ctx context.Context) (*workflow.ConnectorEvent, workflow return nil, nil, ctx.Err() } -var errReachedHeadOfStream = errors.New("reached head of stream", j.C("ERR_547682425078cf6d")) +var errReachedHeadOfStream = errors.New("reached head of stream") func (c *consumer) next() (*workflow.ConnectorEvent, error) { c.mu.Lock() @@ -88,7 +87,7 @@ func (c *consumer) next() (*workflow.ConnectorEvent, error) { cursorOffset := c.cursorStore.Get(c.cursorName) if len(log)-1 < cursorOffset { - return nil, errors.Wrap(errReachedHeadOfStream, "") + return nil, errReachedHeadOfStream } return log[cursorOffset], nil diff --git a/adapters/reflexstreamer/connector.go b/adapters/reflexstreamer/connector.go index 6c020e8..20e43e2 100644 --- a/adapters/reflexstreamer/connector.go +++ b/adapters/reflexstreamer/connector.go @@ -7,7 +7,6 @@ import ( "github.com/luno/jettison/errors" "github.com/luno/jettison/j" "github.com/luno/reflex" - "github.com/luno/workflow" ) @@ -87,7 +86,7 @@ func (c consumer) Close() error { // Provide new context for flushing of cursor values to underlying store err := c.cursorStore.Flush(context.Background()) if err != nil { - return errors.Wrap(err, "failed here") + return errors.Wrap(err, "failed to flush cursor") } if closer, ok := c.streamClient.(io.Closer); ok { diff --git a/adapters/reflexstreamer/go.mod b/adapters/reflexstreamer/go.mod index f8b7b57..91a4927 100644 --- a/adapters/reflexstreamer/go.mod +++ b/adapters/reflexstreamer/go.mod @@ -6,7 +6,6 @@ replace github.com/luno/workflow => ../.. require ( github.com/corverroos/truss v0.0.0-20210514115035-9e5e1b0851a9 - github.com/luno/fate v0.0.0-20240704124432-6dabca7dee1e github.com/luno/jettison v0.0.0-20240625085333-8727b580c646 github.com/luno/reflex v0.0.0-20240709164918-c0516a8a3840 github.com/luno/workflow v0.0.0-20240716150028-e4650a2adf6a diff --git a/adapters/reflexstreamer/go.sum b/adapters/reflexstreamer/go.sum index cabe14c..9fa5920 100644 --- a/adapters/reflexstreamer/go.sum +++ b/adapters/reflexstreamer/go.sum @@ -1,9 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -19,9 +15,6 @@ github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -32,29 +25,20 @@ github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -62,41 +46,21 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/luno/fate v0.0.0-20240704124432-6dabca7dee1e h1:WI3pvfy6ERV03Y0U0gtxxkVlmSUSo+XJAdXVg6A25V8= -github.com/luno/fate v0.0.0-20240704124432-6dabca7dee1e/go.mod h1:vG2oK2pdu8xYXGMMQRYmtLahQ3iToFTjsHkbp0e4MxY= -github.com/luno/jettison v0.0.0-20190815135910-8324430a089d/go.mod h1:tOyVRFDlkZ5NqB7unEwcmrqMqsy+ka3DEtvwH+Q5W6g= github.com/luno/jettison v0.0.0-20210218093327-95083f929b49/go.mod h1:IVntidW2Z2luwfdIpwgvUZSwnRk82RRPfoj0h2P7W+c= github.com/luno/jettison v0.0.0-20240625085333-8727b580c646 h1:9U2KNY3ZeE5uC6d8Nc/Sn9ONUwLirAG1hYQg493i7kE= github.com/luno/jettison v0.0.0-20240625085333-8727b580c646/go.mod h1:cV8KOstEDY+Su4dcN1dadoXC7xmyEqtXAw6Nywia/z8= github.com/luno/reflex v0.0.0-20240709164918-c0516a8a3840 h1:YOGtx4xIzX/F1ihyErF0+SITzVSkurP3rDb/SdzEbRs= github.com/luno/reflex v0.0.0-20240709164918-c0516a8a3840/go.mod h1:FdFAF2wOACOnkxOb5OUx3lNxHW4hCAaulnoJzalphpY= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= @@ -108,12 +72,9 @@ github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvK github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -125,26 +86,19 @@ go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDs go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp/errors v0.0.0-20190306152737-a1d7652674e8/go.mod h1:YgqsNsAu4fTvlab/7uiYK9LJrCIzKg/NiZUIH1/ayqo= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -165,13 +119,11 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/adapters/reflexstreamer/reflex_test.go b/adapters/reflexstreamer/reflex_test.go index dd3546d..27aef2b 100644 --- a/adapters/reflexstreamer/reflex_test.go +++ b/adapters/reflexstreamer/reflex_test.go @@ -100,7 +100,7 @@ func TestStreamFunc(t *testing.T) { fid := "23847923847" _, err := wf.Trigger(ctx, fid, statusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.Require(t, wf, fid, statusEnd, "Started and Completed in a Workflow") @@ -142,14 +142,14 @@ func TestConnector(t *testing.T) { ctx := context.Background() tx, err := dbc.BeginTx(ctx, nil) - jtest.RequireNil(t, err) + require.Nil(t, err) for _, event := range seedEvents { notify, err := eventsTable.Insert(ctx, tx, event.ForeignID, reflexstreamer.EventType(1)) if err != nil { originalErr := err err = tx.Rollback() - jtest.RequireNil(t, err) + require.Nil(t, err) t.Fatal("failed to insert event", event.ForeignID, originalErr.Error()) } @@ -157,7 +157,7 @@ func TestConnector(t *testing.T) { } err = tx.Commit() - jtest.RequireNil(t, err) + require.Nil(t, err) return reflexstreamer.NewConnector(eventsTable.ToStream(dbc), cTable.ToStore(dbc), reflexstreamer.DefaultReflexTranslator) }) diff --git a/adapters/reflexstreamer/util_test.go b/adapters/reflexstreamer/util_test.go index e0b07f8..a2be1d6 100644 --- a/adapters/reflexstreamer/util_test.go +++ b/adapters/reflexstreamer/util_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/corverroos/truss" - "github.com/luno/jettison/jtest" "github.com/luno/reflex" "github.com/stretchr/testify/require" @@ -63,10 +62,10 @@ func TestDefaultTranslators(t *testing.T) { } connectorEvent, err := reflexstreamer.DefaultReflexTranslator(&reflexEvent) - jtest.RequireNil(t, err) + require.Nil(t, err) translated, err := reflexstreamer.DefaultConnectorTranslator(connectorEvent) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, reflexEvent.ID, translated.ID) require.Equal(t, reflexEvent.Type.ReflexType(), translated.Type.ReflexType()) diff --git a/autopause.go b/autopause.go new file mode 100644 index 0000000..89db80c --- /dev/null +++ b/autopause.go @@ -0,0 +1,47 @@ +package workflow + +import ( + "context" + "strconv" + + "github.com/luno/workflow/internal/errorcounter" +) + +// maybePause will either return a nil error if it has failed to pause the record and should be retried. A non-nil +// error is returned when no faults have taken place and the corresponding bool returns true when the Run is paused +// and returns false when the Run was not paused. +func maybePause[Type any, Status StatusType]( + ctx context.Context, + pauseAfterErrCount int, + counter errorcounter.ErrorCounter, + originalErr error, + processName string, + run *Run[Type, Status], + logger *logger, +) (paused bool, err error) { + // Only keep track of errors only if we need to + if pauseAfterErrCount == 0 { + return false, nil + } + + count := counter.Add(originalErr, processName, run.RunID) + if count <= pauseAfterErrCount { + return false, nil + } + + _, err = run.Pause(ctx) + if err != nil { + return false, err + } + + logger.maybeDebug(ctx, "paused record after exceeding allowed error count", map[string]string{ + "record_id": strconv.FormatInt(run.Record.ID, 10), + "workflow_name": run.WorkflowName, + "foreign_id": run.ForeignID, + "run_id": run.RunID, + }) + + // Run paused - now clear the error counter. + counter.Clear(originalErr, processName, run.RunID) + return true, nil +} diff --git a/autopause_internal_test.go b/autopause_internal_test.go new file mode 100644 index 0000000..482c25b --- /dev/null +++ b/autopause_internal_test.go @@ -0,0 +1,88 @@ +package workflow + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/luno/workflow/internal/errorcounter" +) + +func Test_maybeAutoPause(t *testing.T) { + ctx := context.Background() + counter := errorcounter.New() + testErr := errors.New("test error") + pauseErr := errors.New("pause error") + processName := "process" + + testCases := []struct { + name string + countBeforePause int + pausesRecord bool + errCount int + expectedErr error + pauseFn func(ctx context.Context) error + }{ + { + name: "Default - not configured, no previous errors - does not pause workflow run", + countBeforePause: 0, + pausesRecord: false, + errCount: 0, + }, + { + name: "Default - not configured, has previous errors - does not pause workflow run", + countBeforePause: 0, + pausesRecord: false, + errCount: 100, + }, + { + name: "Pause record that has max retry of 1", + countBeforePause: 1, + pausesRecord: true, + errCount: 1, + }, + { + name: "Do not pause record that has not passed max retry", + countBeforePause: 2, + pausesRecord: false, + errCount: 1, + }, + { + name: "Return error when pause fails", + countBeforePause: 1, + pausesRecord: false, + errCount: 1, + pauseFn: func(ctx context.Context) error { + return pauseErr + }, + expectedErr: pauseErr, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := NewTestingRun[string, testStatus](t, Record{ + RunID: "run-id", + }, "test", WithPauseFn(tc.pauseFn)) + + counter.Clear(testErr, processName, r.RunID) + for range tc.errCount { + counter.Add(testErr, processName, r.RunID) + } + + paused, err := maybePause( + ctx, + tc.countBeforePause, + counter, + testErr, + processName, + &r, + &logger{}, + ) + require.ErrorIs(t, err, tc.expectedErr) + require.Equal(t, tc.pausesRecord, paused) + }) + } +} diff --git a/await.go b/await.go index 7e88a41..c2495f6 100644 --- a/await.go +++ b/await.go @@ -2,10 +2,9 @@ package workflow import ( "context" + "errors" "strconv" "time" - - "github.com/luno/jettison/errors" ) func (w *Workflow[Type, Status]) Await(ctx context.Context, foreignID, runID string, status Status, opts ...AwaitOption) (*Run[Type, Status], error) { diff --git a/await_test.go b/await_test.go index 64a3187..1411023 100644 --- a/await_test.go +++ b/await_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -35,10 +34,10 @@ func TestAwait(t *testing.T) { t.Cleanup(wf.Stop) runID, err := wf.Trigger(ctx, "1", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) res, err := wf.Await(ctx, "1", runID, StatusEnd, workflow.WithAwaitPollingFrequency(10*time.Nanosecond)) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, StatusEnd, res.Status) require.Equal(t, "hello world", *res.Object) diff --git a/builder.go b/builder.go index 93b45d0..28b79d9 100644 --- a/builder.go +++ b/builder.go @@ -2,12 +2,14 @@ package workflow import ( "context" + "os" "time" "k8s.io/utils/clock" "github.com/luno/workflow/internal/errorcounter" "github.com/luno/workflow/internal/graph" + interal_logger "github.com/luno/workflow/internal/logger" ) const ( @@ -31,6 +33,10 @@ func NewBuilder[Type any, Status StatusType](name string) *Builder[Type, Status] statusGraph: graph.New(), errorCounter: errorcounter.New(), internalState: make(map[string]State), + logger: &logger{ + debugMode: false, // Explicit for readability + inner: interal_logger.New(os.Stdout), + }, }, } } @@ -201,7 +207,11 @@ func (b *Builder[Type, Status]) Build(eventStreamer EventStreamer, recordStore R b.workflow.timeoutStore = bo.timeoutStore b.workflow.defaultOpts = bo.defaultOptions b.workflow.outboxConfig = bo.outboxConfig - b.workflow.debugMode = bo.debugMode + b.workflow.logger.debugMode = bo.debugMode + + if bo.logger != nil { + b.workflow.logger.inner = bo.logger + } if len(b.workflow.timeouts) > 0 && b.workflow.timeoutStore == nil { panic("cannot configure timeouts without providing TimeoutStore for workflow") @@ -217,6 +227,7 @@ type buildOptions struct { defaultOptions options outboxConfig outboxConfig timeoutStore TimeoutStore + logger Logger } func defaultBuildOptions() buildOptions { @@ -228,24 +239,40 @@ func defaultBuildOptions() buildOptions { type BuildOption func(w *buildOptions) +// WithTimeoutStore allows the configuration of a TimeoutStore which is required when using timeouts in a workflow. It is +// not required by default as timeouts are less common of a feature requirement but when needed the abstraction +// of complexity of handling scheduling, expiring, and executing are incredibly useful and is included as one of the +// three key feature offerings of workflow which are sequential steps, callbacks, and timeouts. func WithTimeoutStore(s TimeoutStore) BuildOption { return func(w *buildOptions) { w.timeoutStore = s } } +// WithClock allows the configuring of workflow's use and access of time. Instead of using time.Now() and other +// associated functionality from the time package a clock is used instead in order to make it testable. func WithClock(c clock.Clock) BuildOption { return func(bo *buildOptions) { bo.clock = c } } +// WithDebugMode enabled debug mode for a workflow which results in increased logs such as when processes ar launched, +// shutdown, events are skipped etc. func WithDebugMode() BuildOption { return func(bo *buildOptions) { bo.debugMode = true } } +// WithLogger allows for specifying a custom logger. The default is to use a wrapped version of log/slog's Logger. +func WithLogger(l Logger) BuildOption { + return func(bo *buildOptions) { + bo.logger = l + } +} + +// WithDefaultOptions applies the provided options to the entire workflow and not just to an individual process. func WithDefaultOptions(opts ...Option) BuildOption { return func(bo *buildOptions) { var o options @@ -257,6 +284,9 @@ func WithDefaultOptions(opts ...Option) BuildOption { } } +// WithCustomDelete allows for specifying a custom deleter function for scrubbing PII data when a workflow Run enters +// RunStateRequestedDataDeleted and is the function that once executed successfully allows for the RunState to move to +// RunStateDataDeleted. func WithCustomDelete[Type any](fn func(object *Type) error) BuildOption { return func(bo *buildOptions) { bo.customDelete = func(wr *Record) ([]byte, error) { @@ -276,21 +306,6 @@ func WithCustomDelete[Type any](fn func(object *Type) error) BuildOption { } } -func (b *Builder[Type, Status]) determineEndPoints(graph map[int][]int) map[Status]bool { - endpoints := make(map[Status]bool) - for _, destinations := range graph { - for _, destination := range destinations { - _, ok := graph[destination] - if !ok { - // end points are nodes that do not have any of their own transitions to transition to. - endpoints[Status(destination)] = true - } - } - } - - return endpoints -} - func DurationTimerFunc[Type any, Status StatusType](duration time.Duration) TimerFunc[Type, Status] { return func(ctx context.Context, r *Run[Type, Status], now time.Time) (time.Time, error) { return now.Add(duration), nil diff --git a/builder_test.go b/builder_internal_test.go similarity index 86% rename from builder_test.go rename to builder_internal_test.go index 414b790..8a5b9e6 100644 --- a/builder_test.go +++ b/builder_internal_test.go @@ -2,18 +2,21 @@ package workflow import ( "context" + "errors" "fmt" "io" + "os" "reflect" "runtime" + "strings" "testing" "time" - "github.com/luno/jettison/errors" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" "github.com/luno/workflow/internal/graph" + internal_logger "github.com/luno/workflow/internal/logger" ) type testStatus int @@ -107,6 +110,52 @@ func TestWithClock(t *testing.T) { require.Equal(t, now.Add(time.Hour), wf.clock.Now()) } +func TestBuildOptions(t *testing.T) { + now := time.Now() + clock := clock_testing.NewFakeClock(now) + timeoutStore := (TimeoutStore)(nil) + logger := internal_logger.New(os.Stdout) + opts := options{ + parallelCount: 2, + pollingFrequency: time.Millisecond, + errBackOff: time.Second, + lag: time.Minute, + lagAlert: time.Hour, + customLagAlertSet: true, + pauseAfterErrCount: 3, + } + deleter := func(object *string) error { + return nil + } + + b := NewBuilder[string, testStatus]("determine starting points") + w := b.Build( + nil, + nil, + nil, + WithTimeoutStore(timeoutStore), + WithClock(clock), + WithDebugMode(), + WithLogger(logger), + WithDefaultOptions( + ParallelCount(opts.parallelCount), + PollingFrequency(opts.pollingFrequency), + ErrBackOff(opts.errBackOff), + ConsumeLag(opts.lag), + LagAlert(opts.lagAlert), + PauseAfterErrCount(opts.pauseAfterErrCount), + ), + WithCustomDelete(deleter), + ) + + require.Equal(t, timeoutStore, w.timeoutStore) + require.Equal(t, clock, w.clock) + require.True(t, w.logger.debugMode) + require.Equal(t, logger, w.logger.inner) + require.Equal(t, opts, w.defaultOpts) + require.True(t, strings.Contains(runtime.FuncForPC(reflect.ValueOf(w.customDelete).Pointer()).Name(), "github.com/luno/workflow.TestBuildOptions.WithCustomDelete")) +} + func TestAddingCallbacks(t *testing.T) { var exampleFn CallbackFunc[string, testStatus] = func(ctx context.Context, s *Run[string, testStatus], r io.Reader) (testStatus, error) { return statusEnd, nil diff --git a/callback.go b/callback.go index 8ee8af7..c2d6509 100644 --- a/callback.go +++ b/callback.go @@ -4,10 +4,7 @@ import ( "bytes" "context" "io" - - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" - "github.com/luno/jettison/log" + "strconv" ) type callback[Type any, Status StatusType] struct { @@ -44,9 +41,7 @@ func processCallback[Type any, Status StatusType]( ) error { wr, err := latest(ctx, w.Name, foreignID) if err != nil { - return errors.Wrap(err, "failed to latest record for callback", j.MKV{ - "foreign_id": foreignID, - }) + return err } if Status(wr.Status) != currentStatus { @@ -54,7 +49,7 @@ func processCallback[Type any, Status StatusType]( return nil } - record, err := buildConsumableRecord[Type, Status](store, wr) + run, err := buildRun[Type, Status](store, wr) if err != nil { return err } @@ -65,25 +60,24 @@ func processCallback[Type any, Status StatusType]( payload = bytes.NewReader([]byte{}) } - next, err := fn(ctx, record, payload) + next, err := fn(ctx, run, payload) if err != nil { return err } if skipUpdate(next) { - if w.debugMode { - log.Info(ctx, "skipping update", j.MKV{ - "description": skipUpdateDescription(next), - "record_id": record.ID, - "workflow_name": w.Name, - "foreign_id": record.ForeignID, - "run_id": record.RunID, - "run_state": record.RunState.String(), - "record_status": record.Status.String(), - }) - } + w.logger.maybeDebug(ctx, "skipping update", map[string]string{ + "description": skipUpdateDescription(next), + "record_id": strconv.FormatInt(run.Record.ID, 10), + "workflow_name": w.Name, + "foreign_id": run.ForeignID, + "run_id": run.RunID, + "run_state": run.RunState.String(), + "record_status": run.Status.String(), + }) + return nil } - return updater(ctx, currentStatus, next, record) + return updater(ctx, currentStatus, next, run) } diff --git a/callback_internal_test.go b/callback_internal_test.go index 1178439..e1e9031 100644 --- a/callback_internal_test.go +++ b/callback_internal_test.go @@ -2,12 +2,11 @@ package workflow import ( "context" + "errors" "io" "testing" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -21,13 +20,14 @@ func TestProcessCallback(t *testing.T) { ctx: ctx, clock: clock_testing.NewFakeClock(time.Date(2024, time.April, 19, 0, 0, 0, 0, time.UTC)), statusGraph: graph.New(), + logger: &logger{}, } w.statusGraph.AddTransition(int(statusStart), int(statusEnd)) value := "data" b, err := Marshal(&value) - jtest.RequireNil(t, err) + require.Nil(t, err) current := &Record{ ID: 1, @@ -79,7 +79,7 @@ func TestProcessCallback(t *testing.T) { } err := processCallback(ctx, w, testStatus(current.Status), callbackFn, current.ForeignID, nil, latestLookup, store, updater) - jtest.RequireNil(t, err) + require.Nil(t, err) expectedCalls := map[string]int{ "callbackFunc": 1, @@ -119,7 +119,7 @@ func TestProcessCallback(t *testing.T) { } err := processCallback(ctx, w, testStatus(current.Status), callbackFn, current.ForeignID, nil, latestLookup, store, updater) - jtest.RequireNil(t, err) + require.Nil(t, err) expectedCalls := map[string]int{ "callbackFunc": 1, @@ -157,7 +157,7 @@ func TestProcessCallback(t *testing.T) { } err := processCallback(ctx, w, testStatus(current.Status), callbackFn, current.ForeignID, nil, latestLookup, store, updater) - jtest.RequireNil(t, err) + require.Nil(t, err) expectedCalls := map[string]int{ "callbackFunc": 1, @@ -168,12 +168,13 @@ func TestProcessCallback(t *testing.T) { }) t.Run("Return on lookup error", func(t *testing.T) { + testErr := errors.New("test error") latestLookup := func(ctx context.Context, workflowName, foreignID string) (*Record, error) { - return nil, errors.New("test error") + return nil, testErr } err := processCallback(ctx, w, testStatus(current.Status), nil, current.ForeignID, nil, latestLookup, nil, nil) - jtest.Require(t, errors.New("failed to latest record for callback"), err) + require.Truef(t, errors.Is(err, testErr), "actual: %s", err.Error()) }) t.Run("Return on callbackFunc error", func(t *testing.T) { @@ -186,7 +187,7 @@ func TestProcessCallback(t *testing.T) { }) err := processCallback(ctx, w, testStatus(current.Status), callbackFn, current.ForeignID, nil, latestLookup, nil, nil) - jtest.Require(t, errors.New("test error"), err) + require.Equal(t, errors.New("test error"), err) }) t.Run("Ignore if record is in different state", func(t *testing.T) { @@ -199,6 +200,6 @@ func TestProcessCallback(t *testing.T) { } err := processCallback(ctx, w, statusStart, nil, current.ForeignID, nil, latestLookup, nil, nil) - jtest.RequireNil(t, err) + require.Nil(t, err) }) } diff --git a/consumer.go b/consumer.go index 69554d0..42881f9 100644 --- a/consumer.go +++ b/consumer.go @@ -2,13 +2,11 @@ package workflow import ( "context" + "errors" + "fmt" "strconv" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" - "github.com/luno/jettison/log" - "github.com/luno/workflow/internal/metrics" ) @@ -135,23 +133,23 @@ func consumeForever[Type any, Status StatusType]( shardFilter(shard, totalShards), ) if shouldFilter { - metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "filtered out").Inc() err = ack() if err != nil { return err } + metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "filtered out").Inc() continue } record, err := w.recordStore.Lookup(ctx, e.ForeignID) if errors.Is(err, ErrRecordNotFound) { - metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record not found").Inc() err = ack() if err != nil { return err } + metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record not found").Inc() continue } else if err != nil { return err @@ -160,34 +158,32 @@ func consumeForever[Type any, Status StatusType]( // Check to see if record is in expected state. If the status isn't in the expected state then skip for // idempotency. if record.Status != int(status) { - metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record status not in expected state").Inc() err = ack() if err != nil { return err } + metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record status not in expected state").Inc() continue } if record.RunState.Stopped() { - if w.debugMode { - log.Info(ctx, "Skipping consumption of stopped workflow record", j.MKV{ - "event_id": e.ID, - "workflow": record.WorkflowName, - "run_id": record.RunID, - "foreign_id": record.ForeignID, - "process_name": processName, - "current_status": record.Status, - "run_state": record.RunState.String(), - }) - } + w.logger.maybeDebug(ctx, "Skipping consumption of stopped workflow record", map[string]string{ + "event_id": strconv.FormatInt(e.ID, 10), + "workflow": record.WorkflowName, + "run_id": record.RunID, + "foreign_id": record.ForeignID, + "process_name": processName, + "current_status": strconv.FormatInt(int64(record.Status), 10), + "run_state": record.RunState.String(), + }) - metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record stopped").Inc() err = ack() if err != nil { return err } + metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "record stopped").Inc() continue } @@ -226,60 +222,51 @@ func consume[Type any, Status StatusType]( processName string, pauseAfterErrCount int, ) error { - record, err := buildConsumableRecord[Type, Status](store, current) + run, err := buildRun[Type, Status](store, current) if err != nil { return err } - next, err := cf(ctx, record) + next, err := cf(ctx, run) if err != nil { - // Only keep track of errors if we need to - if pauseAfterErrCount > 0 { - count := w.errorCounter.Add(err, processName, record.RunID) - if count >= pauseAfterErrCount { - originalErr := err - _, err := record.Pause(ctx) - if err != nil { - return errors.Wrap(err, "failed to pause record after exceeding allowed error count", j.MKV{ - "workflow_name": record.WorkflowName, - "foreign_id": record.ForeignID, - "current_status": record.Status.String(), - "current_status_int": record.Status, - }) - } - - // Run paused - now clear the error counter. - w.errorCounter.Clear(originalErr, processName, record.RunID) - return ack() - } + originalErr := err + paused, err := maybePause(ctx, pauseAfterErrCount, w.errorCounter, originalErr, processName, run, w.logger) + if err != nil { + return fmt.Errorf("pause error: %v, meta: %v", err, map[string]string{ + "run_id": current.RunID, + "foreign_id": current.ForeignID, + }) } - return errors.Wrap(err, "failed to consume", j.MKV{ - "workflow_name": record.WorkflowName, - "foreign_id": record.ForeignID, - "current_status": record.Status.String(), - "current_status_int": record.Status, + if paused { + // Move onto the next event as a record has been paused and a new event is emitted + // when it is resumed. + return ack() + } + + // The record was not paused and the original error is not nil. Pass back up for retrying. + return fmt.Errorf("consumer error: %v, meta: %v", originalErr, map[string]string{ + "run_id": current.RunID, + "foreign_id": current.ForeignID, }) } if skipUpdate(next) { - if w.debugMode { - log.Info(ctx, "skipping update", j.MKV{ - "description": skipUpdateDescription(next), - "record_id": record.ID, - "workflow_name": w.Name, - "foreign_id": record.ForeignID, - "run_id": record.RunID, - "run_state": record.RunState.String(), - "record_status": record.Status.String(), - }) - } + w.logger.maybeDebug(ctx, "skipping update", map[string]string{ + "description": skipUpdateDescription(next), + "record_id": strconv.FormatInt(run.Record.ID, 10), + "workflow_name": w.Name, + "foreign_id": run.ForeignID, + "run_id": run.RunID, + "run_state": run.RunState.String(), + "record_status": run.Status.String(), + }) metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "next value specified skip").Inc() return ack() } - err = updater(ctx, Status(current.Status), next, record) + err = updater(ctx, Status(current.Status), next, run) if err != nil { return err } diff --git a/consumer_internal_test.go b/consumer_internal_test.go index 1497139..e3afcb3 100644 --- a/consumer_internal_test.go +++ b/consumer_internal_test.go @@ -2,29 +2,35 @@ package workflow import ( "context" + "errors" + "os" "testing" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" "github.com/luno/workflow/internal/errorcounter" + internal_logger "github.com/luno/workflow/internal/logger" ) func TestConsume(t *testing.T) { ctx := context.Background() + counter := errorcounter.New() + processName := "processName" + testErr := errors.New("test error") w := &Workflow[string, testStatus]{ Name: "example", ctx: ctx, clock: clock_testing.NewFakeClock(time.Date(2024, time.April, 19, 0, 0, 0, 0, time.UTC)), - errorCounter: errorcounter.New(), + errorCounter: counter, + logger: &logger{ + inner: internal_logger.New(os.Stdout), + }, } - value := "data" b, err := Marshal(&value) - jtest.RequireNil(t, err) + require.Nil(t, err) current := &Record{ ID: 1, @@ -75,8 +81,8 @@ func TestConsume(t *testing.T) { return nil } - err := consume(ctx, w, currentRecord, consumer, ack, store, updater, "processName", 0) - jtest.RequireNil(t, err) + err := consume(ctx, w, currentRecord, consumer, ack, store, updater, processName, 0) + require.Nil(t, err) expectedCalls := map[string]int{ "consumerFunc": 1, @@ -115,8 +121,8 @@ func TestConsume(t *testing.T) { return nil } - err := consume(ctx, w, current, consumer, ack, store, updater, "processName", 0) - jtest.RequireNil(t, err) + err := consume(ctx, w, current, consumer, ack, store, updater, processName, 0) + require.Nil(t, err) expectedCalls := map[string]int{ "consumerFunc": 1, @@ -154,8 +160,8 @@ func TestConsume(t *testing.T) { return nil } - err := consume(ctx, w, current, consumer, ack, store, updater, "processName", 0) - jtest.RequireNil(t, err) + err := consume(ctx, w, current, consumer, ack, store, updater, processName, 0) + require.Nil(t, err) expectedCalls := map[string]int{ "consumerFunc": 1, @@ -168,6 +174,7 @@ func TestConsume(t *testing.T) { t.Run("Pause record after exceeding allowed error count", func(t *testing.T) { t.Parallel() + counter.Clear(testErr, processName, current.RunID) calls := map[string]int{ "consumerFunc": 0, @@ -176,8 +183,6 @@ func TestConsume(t *testing.T) { "store": 0, } - testErr := errors.New("test error") - consumer := ConsumerFunc[string, testStatus](func(ctx context.Context, r *Run[string, testStatus]) (testStatus, error) { calls["consumerFunc"] += 1 return 0, testErr @@ -198,17 +203,20 @@ func TestConsume(t *testing.T) { return nil } - err := consume(ctx, w, current, consumer, ack, store, updater, "processName", 3) - jtest.Require(t, testErr, err) + err := consume(ctx, w, current, consumer, ack, store, updater, processName, 3) + require.NotNil(t, err) + + err = consume(ctx, w, current, consumer, ack, store, updater, processName, 3) + require.NotNil(t, err) - err = consume(ctx, w, current, consumer, ack, store, updater, "processName", 3) - jtest.Require(t, testErr, err) + err = consume(ctx, w, current, consumer, ack, store, updater, processName, 3) + require.NotNil(t, err) - err = consume(ctx, w, current, consumer, ack, store, updater, "processName", 3) - jtest.RequireNil(t, err) + err = consume(ctx, w, current, consumer, ack, store, updater, processName, 3) + require.Nil(t, err) expectedCalls := map[string]int{ - "consumerFunc": 3, + "consumerFunc": 4, "ack": 1, "updater": 0, "store": 1, diff --git a/delete.go b/delete.go index e5cd561..4a4a1b0 100644 --- a/delete.go +++ b/delete.go @@ -2,9 +2,9 @@ package workflow import ( "context" + "errors" "time" - "github.com/luno/jettison/errors" "k8s.io/utils/clock" "github.com/luno/workflow/internal/metrics" @@ -42,7 +42,7 @@ func DeleteForever( c Consumer, store storeFunc, lookup lookupFunc, - deleteFn customDelete, + customDeleteFn customDelete, lagAlert time.Duration, clock clock.Clock, ) error { @@ -77,8 +77,8 @@ func DeleteForever( t2 := clock.Now() replacementData := []byte("{'result': 'deleted'}") // If a custom delete has been configured then use the custom delete - if deleteFn != nil { - bytes, err := deleteFn(record) + if customDeleteFn != nil { + bytes, err := customDeleteFn(record) if err != nil { return err } @@ -90,7 +90,7 @@ func DeleteForever( record.RunState = RunStateDataDeleted err = updateWireRecord(ctx, store, record, RunStateRequestedDataDeleted) if err != nil { - return errors.Wrap(err, "unable to delete record data") + return err } metrics.ProcessLatency.WithLabelValues(workflowName, processName).Observe(clock.Since(t2).Seconds()) diff --git a/delete_test.go b/delete_test.go index 40cd0f8..7a4eed1 100644 --- a/delete_test.go +++ b/delete_test.go @@ -2,11 +2,10 @@ package workflow_test import ( "context" + "errors" "testing" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "k8s.io/utils/clock" @@ -44,7 +43,7 @@ func TestDeleteForever(t *testing.T) { } b, err := workflow.Marshal(&o) - jtest.RequireNil(t, err) + require.Nil(t, err) return &workflow.Record{ Object: b, @@ -55,7 +54,7 @@ func TestDeleteForever(t *testing.T) { var o object err := workflow.Unmarshal(wr.Object, &o) - jtest.RequireNil(t, err) + require.Nil(t, err) o.pii = "" @@ -78,7 +77,7 @@ func TestDeleteForever(t *testing.T) { } b, err := workflow.Marshal(&o) - jtest.RequireNil(t, err) + require.Nil(t, err) return &workflow.Record{ Object: b, @@ -115,7 +114,7 @@ func TestDeleteForever(t *testing.T) { workflowName := "example" producer, err := streamer.NewProducer(ctx, workflow.DeleteTopic(workflowName)) - jtest.RequireNil(t, err) + require.Nil(t, err) t.Cleanup(func() { producer.Close() }) @@ -128,16 +127,16 @@ func TestDeleteForever(t *testing.T) { workflow.HeaderRunState: workflow.RunStateRequestedDataDeleted.String(), workflow.HeaderPreviousRunState: workflow.RunStateCompleted.String(), }) - jtest.RequireNil(t, err) + require.Nil(t, err) consumer, err := streamer.NewConsumer(ctx, workflow.DeleteTopic(workflowName), "consumer-1") - jtest.RequireNil(t, err) + require.Nil(t, err) t.Cleanup(func() { consumer.Close() }) err = workflow.DeleteForever(ctx, workflowName, "process-name", consumer, tc.storeFn, tc.lookupFn, tc.deleteFn, time.Hour, clock.RealClock{}) - jtest.Require(t, tc.expectedErr, err) + require.True(t, errors.Is(err, tc.expectedErr)) }) } } diff --git a/errors.go b/errors.go index 970f34a..644f672 100644 --- a/errors.go +++ b/errors.go @@ -1,20 +1,11 @@ package workflow -import ( - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" -) +import "errors" var ( - ErrRecordNotFound = errors.New("record not found", j.C("ERR_6d982e73339f351a")) - ErrTimeoutNotFound = errors.New("timeout not found", j.C("ERR_2f6c8edf63f7ac8d")) - ErrWorkflowInProgress = errors.New("current workflow still in progress - retry once complete", j.C("ERR_cd79765555450db7")) - ErrWorkflowNotRunning = errors.New("trigger failed - workflow is not running", j.C("ERR_6b414d1eb843a681")) - ErrStatusProvidedNotConfigured = errors.New("status provided is not configured for workflow", j.C("ERR_169c7465995cf7aa")) - ErrOutboxRecordNotFound = errors.New("outbox record not found", j.C("ERR_1ef1afdf9f7ae684")) - ErrUnableToPause = errors.New("run is unable to be paused", j.C("ERR_3b776661fe2c56c7")) - ErrUnableToResume = errors.New("run is unable to be resumed", j.C("ERR_fdbedb1059368f3e")) - ErrUnableToCancel = errors.New("run is unable to be cancelled", j.C("ERR_9128169c3d47eb1d")) - ErrUnableToDelete = errors.New("cannot delete data as run has not finished", j.C("ERR_2dec819246977dd9")) - ErrUnableToMarkAsRunning = errors.New("run is unable to be marked as Running", j.C("ERR_704b88a1eddad3dc")) + ErrRecordNotFound = errors.New("record not found") + ErrTimeoutNotFound = errors.New("timeout not found") + ErrWorkflowInProgress = errors.New("current workflow still in progress - retry once complete") + ErrOutboxRecordNotFound = errors.New("outbox record not found") + ErrInvalidTransition = errors.New("invalid transition") ) diff --git a/eventfilter_test.go b/eventfilter_test.go index 6f30f1d..160f11e 100644 --- a/eventfilter_test.go +++ b/eventfilter_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/google/uuid" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" ) @@ -59,7 +58,7 @@ func TestShardNonNumerical(t *testing.T) { ) for i := 0; i < total; i++ { uid, err := uuid.NewUUID() - jtest.RequireNil(t, err) + require.Nil(t, err) e := &ConnectorEvent{ ID: uid.String(), diff --git a/go.mod b/go.mod index 5442327..387a463 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.22.3 require ( github.com/google/uuid v1.3.0 - github.com/luno/jettison v0.0.0-20230912135954-09d6084f5df9 github.com/prometheus/client_golang v1.15.0 github.com/robfig/cron/v3 v3.0.0 github.com/stretchr/testify v1.8.3 @@ -16,7 +15,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-stack/stack v1.8.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -24,8 +22,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect golang.org/x/sys v0.9.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d0d95b4..a6800c1 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -17,14 +15,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/luno/jettison v0.0.0-20230912135954-09d6084f5df9 h1:ykQ0/3gmOFypz1tNTyisrEM51rW7bx/UCGqEqrm/VyM= -github.com/luno/jettison v0.0.0-20230912135954-09d6084f5df9/go.mod h1:Fc8KRfQEydIud0rjBHUNxUyyEKSidxP0kOlg4Dwn3JA= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= @@ -39,27 +39,21 @@ github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= -github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= diff --git a/internal/errorcounter/errorcounter_test.go b/internal/errorcounter/errorcounter_test.go index a7057d7..798f294 100644 --- a/internal/errorcounter/errorcounter_test.go +++ b/internal/errorcounter/errorcounter_test.go @@ -1,9 +1,9 @@ package errorcounter_test import ( + "errors" "testing" - "github.com/luno/jettison/errors" "github.com/stretchr/testify/require" "github.com/luno/workflow/internal/errorcounter" diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..9c7aff3 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,30 @@ +package logger + +import ( + "context" + "io" + "log/slog" +) + +type logger struct { + log *slog.Logger +} + +func (l logger) Debug(ctx context.Context, msg string, meta map[string]string) { + l.log.DebugContext(ctx, msg, "meta", meta) +} + +func (l logger) Error(ctx context.Context, err error) { + l.log.ErrorContext(ctx, err.Error()) +} + +func New(w io.Writer) *logger { + // LevelDebug is set by default as the workflow has a debug configuration. + opts := slog.HandlerOptions{ + Level: slog.LevelDebug, + } + sl := slog.New(slog.NewJSONHandler(w, &opts)) + return &logger{ + log: sl, + } +} diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go new file mode 100644 index 0000000..be6e548 --- /dev/null +++ b/internal/logger/logger_test.go @@ -0,0 +1,32 @@ +package logger_test + +import ( + "bytes" + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/luno/workflow/internal/logger" +) + +func TestLoggerDebug(t *testing.T) { + var buf bytes.Buffer + log := logger.New(&buf) + + ctx := context.Background() + log.Debug(ctx, "test message", map[string]string{"key": "value"}) + + require.Contains(t, buf.String(), "\"level\":\"DEBUG\",\"msg\":\"test message\",\"meta\":{\"key\":\"value\"}") +} + +func TestLogger_Error(t *testing.T) { + var buf bytes.Buffer + log := logger.New(&buf) + + ctx := context.Background() + log.Error(ctx, errors.New("test error")) + + require.Contains(t, buf.String(), "\"level\":\"ERROR\",\"msg\":\"test error\"") +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..0302b27 --- /dev/null +++ b/logger.go @@ -0,0 +1,40 @@ +package workflow + +import ( + "context" +) + +// Logger interface allows the user of Workflow to provide a custom logger and not use the default which is provided +// in internal/logger. Workflow only writes two types of logs: Debug and Error. Error is only used at the highest +// level where an auto-retry process (consumers and pollers) errors and retries. +// +// Error is used only when the error cannot be passed back to the caller and cannot be bubbled up any further. +// +// Debug is used only when the Workflow is built with WithDebugMode. +type Logger interface { + // Debug will be used by workflow for debug logs when in debug mode. + Debug(ctx context.Context, msg string, meta map[string]string) + // Error is used when writing errors to the logs. + Error(ctx context.Context, err error) +} + +// logger wraps the default logger (internal/logger) or the provided logger (WithLogger) that manages whether a log +// should be written based on the options that the Workflow was built with such as WithDebugMode. +type logger struct { + debugMode bool + inner Logger +} + +// maybeDebug only writes the log if the Workflow was built using WithDebugMode +func (l *logger) maybeDebug(ctx context.Context, msg string, meta map[string]string) { + if !l.debugMode { + return + } + + l.inner.Debug(ctx, msg, meta) +} + +// Error writes the error to the underlying logger +func (l *logger) Error(ctx context.Context, err error) { + l.inner.Error(ctx, err) +} diff --git a/metrics_test.go b/metrics_test.go index bf9f035..5edef31 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -2,14 +2,13 @@ package workflow_test import ( "context" + "errors" "strings" "sync" "testing" "time" "github.com/google/uuid" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -46,13 +45,13 @@ func runWorkflow(t *testing.T) *workflow.Workflow[string, status] { ctx := context.Background() uid, err := uuid.NewUUID() - jtest.RequireNil(t, err) + require.Nil(t, err) runID := uid.String() var s string payload, err := workflow.Marshal(&s) - jtest.RequireNil(t, err) + require.Nil(t, err) err = update(ctx, recordStore, &workflow.Record{ WorkflowName: "example", @@ -63,7 +62,7 @@ func runWorkflow(t *testing.T) *workflow.Workflow[string, status] { Object: payload, CreatedAt: clock.Now(), }) - jtest.RequireNil(t, err) + require.Nil(t, err) // 1 hour = 3600 seconds clock.Step(time.Hour) @@ -89,7 +88,7 @@ workflow_process_lag_seconds{process_name="start-consumer-1-of-1",workflow_name= ` err := testutil.CollectAndCompare(metrics.ConsumerLag, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) metrics.ConsumerLag.Reset() } @@ -123,7 +122,7 @@ workflow_process_states{process_name="start-consumer-1-of-1",workflow_name="exam ` err := testutil.CollectAndCompare(metrics.ProcessStates, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) w.Stop() @@ -138,7 +137,7 @@ workflow_process_states{process_name="outbox-consumer-2-of-2",workflow_name="exa ` err = testutil.CollectAndCompare(metrics.ProcessStates, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) metrics.ProcessStates.Reset() } @@ -222,7 +221,7 @@ workflow_process_states{process_name="outbox-consumer-1-of-1",workflow_name="exa ` err := testutil.CollectAndCompare(metrics.ProcessStates, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) scheduler.mu.Lock() scheduler.allow = true @@ -241,7 +240,7 @@ workflow_process_states{process_name="outbox-consumer-1-of-1",workflow_name="exa ` err = testutil.CollectAndCompare(metrics.ProcessStates, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) wf.Stop() @@ -256,7 +255,7 @@ workflow_process_states{process_name="outbox-consumer-1-of-1",workflow_name="exa ` err = testutil.CollectAndCompare(metrics.ProcessStates, strings.NewReader(expected)) - jtest.RequireNil(t, err) + require.Nil(t, err) metrics.ProcessStates.Reset() } @@ -298,13 +297,13 @@ func TestMetricProcessErrors(t *testing.T) { ctx := context.Background() uid, err := uuid.NewUUID() - jtest.RequireNil(t, err) + require.Nil(t, err) runID := uid.String() var s string payload, err := workflow.Marshal(&s) - jtest.RequireNil(t, err) + require.Nil(t, err) err = update(ctx, recordStore, &workflow.Record{ WorkflowName: "example", @@ -315,7 +314,7 @@ func TestMetricProcessErrors(t *testing.T) { Object: payload, CreatedAt: clock.Now(), }) - jtest.RequireNil(t, err) + require.Nil(t, err) wf.Run(ctx) t.Cleanup(wf.Stop) @@ -358,7 +357,7 @@ func TestRunStateChanges(t *testing.T) { t.Cleanup(w.Stop) _, err := w.Trigger(ctx, "983467934", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) time.Sleep(time.Millisecond * 500) @@ -391,13 +390,13 @@ func TestMetricProcessSkippedEvents(t *testing.T) { t.Cleanup(w.Stop) _, err := w.Trigger(ctx, "9834679343", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) _, err = w.Trigger(ctx, "2349839483", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) _, err = w.Trigger(ctx, "7548702398", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) time.Sleep(time.Millisecond * 500) diff --git a/options.go b/options.go index 77a1979..97bc0ca 100644 --- a/options.go +++ b/options.go @@ -74,8 +74,8 @@ func ConsumeLag(d time.Duration) Option { } // PauseAfterErrCount defines the number of times an error can occur until the record is updated to RunStatePaused -// which is similar to a Dead Letter Queue in the sense that the record will no longer be processed and wont block -// workflow consumers and can be investigated and retried. +// which is similar to a Dead Letter Queue in the sense that the record will no longer be processed and won't block +// the workflow's consumers and can be investigated and retried later on. func PauseAfterErrCount(count int) Option { return func(opt *options) { opt.pauseAfterErrCount = count diff --git a/outbox.go b/outbox.go index 24b27ee..7e46a8b 100644 --- a/outbox.go +++ b/outbox.go @@ -5,7 +5,6 @@ import ( "strconv" "time" - "github.com/luno/jettison/errors" "google.golang.org/protobuf/proto" "k8s.io/utils/clock" @@ -124,7 +123,7 @@ func purgeOutbox[Type any, Status StatusType]( var outboxRecord outboxpb.OutboxRecord err := proto.Unmarshal(e.Data, &outboxRecord) if err != nil { - return errors.Wrap(err, "Unable to proto unmarshal outbox record") + return err } headers := make(map[Header]string) @@ -153,12 +152,12 @@ func purgeOutbox[Type any, Status StatusType]( topic := headers[HeaderTopic] producer, err := streamer.NewProducer(ctx, topic) if err != nil { - return errors.Wrap(err, "Unable to construct new producer for outbox purging") + return err } err = producer.Send(ctx, event.ForeignID, event.Type, event.Headers) if err != nil { - return errors.Wrap(err, "Unable to send outbox event to event streamer") + return err } err = recordStore.DeleteOutboxEvent(ctx, event.ID) diff --git a/run.go b/run.go index a0a7d60..b6b42aa 100644 --- a/run.go +++ b/run.go @@ -2,7 +2,6 @@ package workflow import ( "context" - "testing" ) // Run is a representation of a workflow run. It incorporates all the fields from the Record as well as @@ -47,18 +46,7 @@ func (r *Run[Type, Status]) Cancel(ctx context.Context) (Status, error) { return Status(SkipTypeRunStateUpdate), nil } -// NewTestingRun should be used when testing logic that defines a workflow.Run as a parameter. This is usually the -// case in unit tests and would not normally be found when doing an Acceptance test for the entire workflow. -func NewTestingRun[Type any, Status StatusType](t *testing.T, wr Record, object Type) Run[Type, Status] { - return Run[Type, Status]{ - Record: wr, - Status: Status(wr.Status), - Object: &object, - controller: &noopRunStateController{}, - } -} - -func buildConsumableRecord[Type any, Status StatusType](store storeFunc, wr *Record) (*Run[Type, Status], error) { +func buildRun[Type any, Status StatusType](store storeFunc, wr *Record) (*Run[Type, Status], error) { var t Type err := Unmarshal(wr.Object, &t) if err != nil { diff --git a/run_test.go b/run_test.go index c411270..52f3f7c 100644 --- a/run_test.go +++ b/run_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -15,10 +14,10 @@ func TestNewTestingRun(t *testing.T) { ctx := context.Background() pauseStatus, err := r.Pause(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, status(workflow.SkipTypeRunStateUpdate), pauseStatus) cancelStatus, err := r.Cancel(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, status(workflow.SkipTypeRunStateUpdate), cancelStatus) } diff --git a/runstate.go b/runstate.go index 958e580..7973ab7 100644 --- a/runstate.go +++ b/runstate.go @@ -2,10 +2,8 @@ package workflow import ( "context" + "fmt" "strconv" - - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" ) type RunState int @@ -104,25 +102,25 @@ type runStateControllerImpl struct { } func (rsc *runStateControllerImpl) Pause(ctx context.Context) error { - return rsc.update(ctx, RunStatePaused, ErrUnableToPause) + return rsc.update(ctx, RunStatePaused) } func (rsc *runStateControllerImpl) Resume(ctx context.Context) error { - return rsc.update(ctx, RunStateRunning, ErrUnableToResume) + return rsc.update(ctx, RunStateRunning) } func (rsc *runStateControllerImpl) Cancel(ctx context.Context) error { - return rsc.update(ctx, RunStateCancelled, ErrUnableToCancel) + return rsc.update(ctx, RunStateCancelled) } func (rsc *runStateControllerImpl) DeleteData(ctx context.Context) error { - return rsc.update(ctx, RunStateRequestedDataDeleted, ErrUnableToDelete) + return rsc.update(ctx, RunStateRequestedDataDeleted) } -func (rsc *runStateControllerImpl) update(ctx context.Context, rs RunState, invalidTransitionErr error) error { - err := validateRunStateTransition(rsc.record, rs, invalidTransitionErr) - if err != nil { - return err +func (rsc *runStateControllerImpl) update(ctx context.Context, rs RunState) error { + valid, ok := runStateTransitions[rsc.record.RunState] + if !ok || !valid[rs] { + return fmt.Errorf("invalid RunState: %s", rsc.record.RunState) } previousRunState := rsc.record.RunState @@ -130,30 +128,6 @@ func (rsc *runStateControllerImpl) update(ctx context.Context, rs RunState, inva return updateWireRecord(ctx, rsc.store, rsc.record, previousRunState) } -func validateRunStateTransition(record *Record, runState RunState, sentinelErr error) error { - valid, ok := runStateTransitions[record.RunState] - if !ok { - return errors.Wrap(sentinelErr, "current run state is terminal", j.MKV{ - "record_id": record.ID, - "workflow_name": record.WorkflowName, - "run_state": record.RunState.String(), - "run_state_int_value": int(record.RunState), - }) - } - - if !valid[runState] { - msg := "Current run state cannot transition to " + runState.String() - return errors.Wrap(sentinelErr, msg, j.MKV{ - "record_id": record.ID, - "workflow_name": record.WorkflowName, - "run_state": record.RunState.String(), - "run_state_int_value": int(record.RunState), - }) - } - - return nil -} - var runStateTransitions = map[RunState]map[RunState]bool{ RunStateInitiated: { RunStateRunning: true, @@ -181,23 +155,3 @@ var runStateTransitions = map[RunState]map[RunState]bool{ RunStateRequestedDataDeleted: true, }, } - -type noopRunStateController struct{} - -func (c *noopRunStateController) Pause(ctx context.Context) error { - return nil -} - -func (c *noopRunStateController) Cancel(ctx context.Context) error { - return nil -} - -func (c *noopRunStateController) Resume(ctx context.Context) error { - return nil -} - -func (c *noopRunStateController) DeleteData(ctx context.Context) error { - return nil -} - -var _ RunStateController = (*noopRunStateController)(nil) diff --git a/runstate_internal_test.go b/runstate_internal_test.go index f209ca9..4dcd3b3 100644 --- a/runstate_internal_test.go +++ b/runstate_internal_test.go @@ -4,22 +4,22 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" + "github.com/stretchr/testify/require" ) func TestNoopRunStateController(t *testing.T) { - ctrl := noopRunStateController{} + ctrl := testingRunStateController{} ctx := context.Background() err := ctrl.Pause(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) err = ctrl.Resume(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) err = ctrl.Cancel(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) err = ctrl.DeleteData(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) } diff --git a/runstate_test.go b/runstate_test.go index a875208..1b05793 100644 --- a/runstate_test.go +++ b/runstate_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -72,7 +71,7 @@ func TestRunState(t *testing.T) { // Trigger workflow before it's running to assert that the initial state is workflow.RunStateInitiated runID, err := w.Trigger(ctx, "fid", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) time.Sleep(time.Second) @@ -125,7 +124,7 @@ func TestWorkflowRunStateController(t *testing.T) { Name: "Andrew Wormald", Car: "Audi", }) - jtest.RequireNil(t, err) + require.Nil(t, err) ctx := context.Background() workflowName := "test-workflow" @@ -140,49 +139,49 @@ func TestWorkflowRunStateController(t *testing.T) { }) record, err := recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) time.Sleep(time.Millisecond * 500) record, err = recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, workflow.RunStateInitiated, record.RunState) wr, err := recordStore.Lookup(ctx, 1) - jtest.RequireNil(t, err) + require.Nil(t, err) rsc := workflow.NewRunStateController(recordStore.Store, wr) err = rsc.Pause(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) record, err = recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, workflow.RunStatePaused, record.RunState) err = rsc.Resume(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) record, err = recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, workflow.RunStateRunning, record.RunState) err = rsc.Cancel(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) record, err = recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, workflow.RunStateCancelled, record.RunState) err = rsc.DeleteData(ctx) - jtest.RequireNil(t, err) + require.Nil(t, err) record, err = recordStore.Latest(ctx, workflowName, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, workflow.RunStateRequestedDataDeleted, record.RunState) } diff --git a/schedule.go b/schedule.go index a8e757a..7e6f002 100644 --- a/schedule.go +++ b/schedule.go @@ -2,22 +2,24 @@ package workflow import ( "context" + "errors" "fmt" "strconv" "time" - "github.com/luno/jettison/errors" "github.com/robfig/cron/v3" "k8s.io/utils/clock" ) func (w *Workflow[Type, Status]) Schedule(foreignID string, startingStatus Status, spec string, opts ...ScheduleOption[Type, Status]) error { if !w.calledRun { - return errors.Wrap(ErrWorkflowNotRunning, "ensure Run() is called before attempting to trigger the workflow") + return fmt.Errorf("schedule failed: workflow is not running") } if !w.statusGraph.IsValid(int(startingStatus)) { - return errors.Wrap(ErrStatusProvidedNotConfigured, fmt.Sprintf("ensure %v is configured for workflow: %v", startingStatus, w.Name)) + w.logger.maybeDebug(w.ctx, fmt.Sprintf("ensure %v is configured for workflow: %v", startingStatus, w.Name), map[string]string{}) + + return fmt.Errorf("schedule failed: status provided is not configured for workflow: %s", startingStatus) } var options scheduleOpts[Type, Status] diff --git a/schedule_test.go b/schedule_test.go index d5d926e..3169705 100644 --- a/schedule_test.go +++ b/schedule_test.go @@ -2,11 +2,11 @@ package workflow_test import ( "context" + "errors" "sync" "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -47,7 +47,7 @@ func TestSchedule(t *testing.T) { go func() { err := wf.Schedule("andrew", StatusStart, "@monthly") - jtest.RequireNil(t, err) + require.Nil(t, err) }() // Allow scheduling to take place @@ -55,7 +55,7 @@ func TestSchedule(t *testing.T) { _, err := recordStore.Latest(ctx, workflowName, "andrew") // Expect there to be no entries yet - jtest.Require(t, workflow.ErrRecordNotFound, err) + require.True(t, errors.Is(err, workflow.ErrRecordNotFound)) // Grab the time from the clock for expectation as to the time we expect the entry to have expectedTimestamp := time.Date(2023, time.May, 1, 0, 0, 0, 0, time.UTC) @@ -65,10 +65,10 @@ func TestSchedule(t *testing.T) { time.Sleep(200 * time.Millisecond) firstScheduled, err := recordStore.Latest(ctx, workflowName, "andrew") - jtest.RequireNil(t, err) + require.Nil(t, err) _, err = wf.Await(ctx, firstScheduled.ForeignID, firstScheduled.RunID, StatusEnd) - jtest.RequireNil(t, err) + require.Nil(t, err) expectedTimestamp = time.Date(2023, time.June, 1, 0, 0, 0, 0, time.UTC) clock.SetTime(expectedTimestamp) @@ -77,7 +77,7 @@ func TestSchedule(t *testing.T) { time.Sleep(200 * time.Millisecond) secondScheduled, err := recordStore.Latest(ctx, workflowName, "andrew") - jtest.RequireNil(t, err) + require.Nil(t, err) require.NotEqual(t, firstScheduled.RunID, secondScheduled.RunID) } @@ -106,7 +106,7 @@ func TestWorkflow_ScheduleShutdown(t *testing.T) { go func() { wg.Done() err := wf.Schedule("andrew", StatusStart, "@monthly") - jtest.RequireNil(t, err) + require.Nil(t, err) }() wg.Wait() @@ -168,7 +168,7 @@ func TestWorkflow_ScheduleFilter(t *testing.T) { go func() { err := wf.Schedule("andrew", StatusStart, "@monthly", opt) - jtest.RequireNil(t, err) + require.Nil(t, err) }() // Allow scheduling to take place @@ -176,7 +176,7 @@ func TestWorkflow_ScheduleFilter(t *testing.T) { _, err := recordStore.Latest(ctx, workflowName, "andrew") // Expect there to be no entries yet - jtest.Require(t, workflow.ErrRecordNotFound, err) + require.True(t, errors.Is(err, workflow.ErrRecordNotFound)) // Grab the time from the clock for expectation as to the time we expect the entry to have expectedTimestamp := time.Date(2023, time.May, 1, 0, 0, 0, 0, time.UTC) @@ -187,7 +187,7 @@ func TestWorkflow_ScheduleFilter(t *testing.T) { _, err = recordStore.Latest(ctx, workflowName, "andrew") // Expect there to be no entries yet - jtest.Require(t, workflow.ErrRecordNotFound, err) + require.True(t, errors.Is(err, workflow.ErrRecordNotFound)) // Disable the filter to enable scheduling *shouldSkip = true @@ -199,10 +199,10 @@ func TestWorkflow_ScheduleFilter(t *testing.T) { time.Sleep(200 * time.Millisecond) latest, err := recordStore.Latest(ctx, workflowName, "andrew") - jtest.RequireNil(t, err) + require.Nil(t, err) resp, err := wf.Await(ctx, latest.ForeignID, latest.RunID, StatusEnd) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, expectedTimestamp, resp.CreatedAt) } diff --git a/sonar-project.properties b/sonar-project.properties index 66e8067..95106d1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,9 +4,9 @@ sonar.projectName = workflow sonar.links.scm = https://github.com/luno/workflow sonar.sources = . -sonar.exclusions=**/*_test.go, **/*pb.go, _examples/**/*, adapters/reflexstreamer/**/*, adapters/kafkastreamer/**/*, adapters/sqlstore/**/*, adapters/sqltimeout/**/* +sonar.exclusions=**/*_test.go, **/*pb.go, _examples/**/*, adapters/reflexstreamer/**/*, adapters/kafkastreamer/**/*, adapters/sqlstore/**/*, adapters/sqltimeout/**/*, adapters/jlog/**/* sonar.go.coverage.reportPaths = coverage.out sonar.go.tests.reportPaths = sonar-report.json sonar.tests = . sonar.test.inclusions = **/*_test.go -sonar.test.exclusions=_examples/**/*, **/*pb.go, adapters/reflexstreamer/**/*, adapters/kafkastreamer/**/*, adapters/sqlstore/**/*, adapters/sqltimeout/**/* +sonar.test.exclusions=_examples/**/*, **/*pb.go, adapters/reflexstreamer/**/*, adapters/kafkastreamer/**/*, adapters/sqlstore/**/*, adapters/sqltimeout/**/*, adapters/jlog/**/* diff --git a/testing.go b/testing.go index 53c6414..c20affa 100644 --- a/testing.go +++ b/testing.go @@ -4,11 +4,10 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "testing" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" ) @@ -20,13 +19,13 @@ func TriggerCallbackOn[Type any, Status StatusType, Payload any](t testing.TB, w ctx := context.TODO() _, err := w.Await(ctx, foreignID, runID, waitFor) - jtest.RequireNil(t, err) + require.Nil(t, err) b, err := json.Marshal(p) - jtest.RequireNil(t, err) + require.Nil(t, err) err = w.Callback(ctx, foreignID, waitFor, bytes.NewReader(b)) - jtest.RequireNil(t, err) + require.Nil(t, err) } func AwaitTimeoutInsert[Type any, Status StatusType](t testing.TB, w *Workflow[Type, Status], foreignID, runID string, waitFor Status) { @@ -41,7 +40,7 @@ func AwaitTimeoutInsert[Type any, Status StatusType](t testing.TB, w *Workflow[T } ls, err := w.timeoutStore.List(w.ctx, w.Name) - jtest.RequireNil(t, err) + require.Nil(t, err) for _, l := range ls { if l.Status != int(waitFor) { @@ -83,7 +82,7 @@ func Require[Type any, Status StatusType](t testing.TB, w *Workflow[Type, Status if errors.Is(err, ErrRecordNotFound) { continue } else { - jtest.RequireNil(t, err) + require.Nil(t, err) } runID = latest.RunID @@ -108,7 +107,7 @@ func Require[Type any, Status StatusType](t testing.TB, w *Workflow[Type, Status var typ Type err := json.Unmarshal(wr.Object, &typ) - jtest.RequireNil(t, err) + require.Nil(t, err) actual := &Run[Type, Status]{ Record: *wr, @@ -118,3 +117,90 @@ func Require[Type any, Status StatusType](t testing.TB, w *Workflow[Type, Status require.Equal(t, expected, *actual.Object) } + +// NewTestingRun should be used when testing logic that defines a workflow.Run as a parameter. This is usually the +// case in unit tests and would not normally be found when doing an Acceptance test for the entire workflow. +func NewTestingRun[Type any, Status StatusType](t *testing.T, wr Record, object Type, opts ...TestingRunOption) Run[Type, Status] { + var options testingRunOpts + for _, opt := range opts { + opt(&options) + } + + return Run[Type, Status]{ + Record: wr, + Status: Status(wr.Status), + Object: &object, + controller: &options.controller, + } +} + +type testingRunOpts struct { + controller testingRunStateController +} + +type TestingRunOption func(*testingRunOpts) + +func WithPauseFn(pause func(ctx context.Context) error) TestingRunOption { + return func(opts *testingRunOpts) { + opts.controller.pause = pause + } +} + +func WithResumeFn(resume func(ctx context.Context) error) TestingRunOption { + return func(opts *testingRunOpts) { + opts.controller.resume = resume + } +} + +func WithCancelFn(cancel func(ctx context.Context) error) TestingRunOption { + return func(opts *testingRunOpts) { + opts.controller.cancel = cancel + } +} + +func WithDeleteDataFn(deleteData func(ctx context.Context) error) TestingRunOption { + return func(opts *testingRunOpts) { + opts.controller.deleteData = deleteData + } +} + +type testingRunStateController struct { + pause func(ctx context.Context) error + cancel func(ctx context.Context) error + resume func(ctx context.Context) error + deleteData func(ctx context.Context) error +} + +func (c *testingRunStateController) Pause(ctx context.Context) error { + if c.pause == nil { + return nil + } + + return c.pause(ctx) +} + +func (c *testingRunStateController) Cancel(ctx context.Context) error { + if c.cancel == nil { + return nil + } + + return c.cancel(ctx) +} + +func (c *testingRunStateController) Resume(ctx context.Context) error { + if c.resume == nil { + return nil + } + + return c.resume(ctx) +} + +func (c *testingRunStateController) DeleteData(ctx context.Context) error { + if c.deleteData == nil { + return nil + } + + return c.deleteData(ctx) +} + +var _ RunStateController = (*testingRunStateController)(nil) diff --git a/testing_test.go b/testing_test.go index f7fa57d..67d7a58 100644 --- a/testing_test.go +++ b/testing_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" + "github.com/stretchr/testify/require" "github.com/luno/workflow" "github.com/luno/workflow/adapters/memrecordstore" @@ -43,7 +43,7 @@ func TestRequireForCircularStatus(t *testing.T) { fid := "10298309123" _, err := wf.Trigger(ctx, fid, StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.Require(t, wf, fid, StatusStart, Counter{Count: 0}) workflow.Require(t, wf, fid, StatusMiddle, Counter{Count: 1}) diff --git a/timeout.go b/timeout.go index b88d852..db53cb2 100644 --- a/timeout.go +++ b/timeout.go @@ -2,13 +2,10 @@ package workflow import ( "context" + "fmt" "strconv" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" - "github.com/luno/jettison/log" - "github.com/luno/workflow/internal/metrics" ) @@ -64,16 +61,14 @@ func pollTimeouts[Type any, Status StatusType]( } if r.RunState.Stopped() { - if w.debugMode { - log.Info(ctx, "Skipping processing of timeout of stopped workflow record", j.MKV{ - "workflow": r.WorkflowName, - "run_id": r.RunID, - "foreign_id": r.ForeignID, - "process_name": processName, - "current_status": r.Status, - "run_state": r.RunState.String(), - }) - } + w.logger.maybeDebug(ctx, "Skipping processing of timeout of stopped workflow record", map[string]string{ + "workflow": r.WorkflowName, + "run_id": r.RunID, + "foreign_id": r.ForeignID, + "process_name": processName, + "current_status": strconv.FormatInt(int64(r.Status), 10), + "run_state": r.RunState.String(), + }) // Continue to next expired timeout continue @@ -104,7 +99,7 @@ func processTimeout[Type any, Status StatusType]( ctx context.Context, w *Workflow[Type, Status], config timeout[Type, Status], - r *Record, + record *Record, timeout TimeoutRecord, completeFn completeFunc, store storeFunc, @@ -112,59 +107,40 @@ func processTimeout[Type any, Status StatusType]( processName string, pauseAfterErrCount int, ) error { - record, err := buildConsumableRecord[Type, Status](store, r) + run, err := buildRun[Type, Status](store, record) if err != nil { return err } - next, err := config.TimeoutFunc(ctx, record, w.clock.Now()) + next, err := config.TimeoutFunc(ctx, run, w.clock.Now()) if err != nil { - // Only keep track of errors if we need to - if pauseAfterErrCount > 0 { - count := w.errorCounter.Add(err, processName, record.RunID) - if count >= pauseAfterErrCount { - originalErr := err - _, err := record.Pause(ctx) - if err != nil { - return errors.Wrap(err, "failed to pause record after exceeding allowed error count", j.MKV{ - "workflow_name": record.WorkflowName, - "foreign_id": record.ForeignID, - "current_status": record.Status.String(), - "current_status_int": record.Status, - }) - } - - // Run paused - now clear the error counter. - w.errorCounter.Clear(originalErr, processName, record.RunID) - return nil - } + _, err := maybePause(ctx, pauseAfterErrCount, w.errorCounter, err, processName, run, w.logger) + if err != nil { + return fmt.Errorf("pause error: %v, meta: %v", err, map[string]string{ + "run_id": record.RunID, + "foreign_id": record.ForeignID, + }) } - return errors.Wrap(err, "failed to process timeout", j.MKV{ - "workflow_name": record.WorkflowName, - "foreign_id": record.ForeignID, - "current_status": record.Status.String(), - "current_status_int": record.Status, - }) + + return nil } if skipUpdate(next) { - if w.debugMode { - log.Info(ctx, "skipping update", j.MKV{ - "description": skipUpdateDescription(next), - "record_id": record.ID, - "workflow_name": w.Name, - "foreign_id": record.ForeignID, - "run_id": record.RunID, - "run_state": record.RunState.String(), - "record_status": record.Status.String(), - }) - } + w.logger.maybeDebug(ctx, "skipping update", map[string]string{ + "description": skipUpdateDescription(next), + "record_id": strconv.FormatInt(run.Record.ID, 10), + "workflow_name": w.Name, + "foreign_id": run.ForeignID, + "run_id": run.RunID, + "run_state": run.RunState.String(), + "record_status": run.Status.String(), + }) metrics.ProcessSkippedEvents.WithLabelValues(w.Name, processName, "next value specified skip").Inc() return nil } - err = updater(ctx, Status(timeout.Status), next, record) + err = updater(ctx, Status(timeout.Status), next, run) if err != nil { return err } diff --git a/timeout_internal_test.go b/timeout_internal_test.go index 8ddad7f..3107afc 100644 --- a/timeout_internal_test.go +++ b/timeout_internal_test.go @@ -2,11 +2,10 @@ package workflow import ( "context" + "errors" "testing" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -15,16 +14,20 @@ import ( func TestProcessTimeout(t *testing.T) { ctx := context.Background() + counter := errorcounter.New() + processName := "processName" + testErr := errors.New("test error") w := &Workflow[string, testStatus]{ Name: "example", ctx: ctx, clock: clock_testing.NewFakeClock(time.Date(2024, time.April, 19, 0, 0, 0, 0, time.UTC)), - errorCounter: errorcounter.New(), + errorCounter: counter, + logger: &logger{}, } value := "data" b, err := Marshal(&value) - jtest.RequireNil(t, err) + require.Nil(t, err) type calls struct { updater func(ctx context.Context, current testStatus, next testStatus, record *Run[string, testStatus]) error @@ -36,12 +39,12 @@ func TestProcessTimeout(t *testing.T) { type caller func(call map[string]int) calls testCases := []struct { - name string - caller caller - timeout timeout[string, testStatus] - record *Record - expectedCalls map[string]int - expectedError error + name string + caller caller + timeout timeout[string, testStatus] + record *Record + currentErrCount int + expectedCalls map[string]int }{ { name: "Golden path consume - initiated", @@ -151,7 +154,7 @@ func TestProcessTimeout(t *testing.T) { return calls{ timeoutFunc: func(ctx context.Context, r *Run[string, testStatus], now time.Time) (testStatus, error) { call["timeout/TimeoutFunc"] += 1 - return 0, errors.New("test error") + return 0, testErr }, store: func(ctx context.Context, record *Record, maker OutboxEventDataMaker) error { call["store"] += 1 @@ -169,15 +172,20 @@ func TestProcessTimeout(t *testing.T) { Status: int(statusStart), Object: b, }, + currentErrCount: 1, expectedCalls: map[string]int{ "timeout/TimeoutFunc": 1, "store": 1, }, - expectedError: errors.New("test error"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + counter.Clear(testErr, processName, tc.record.RunID) + for range tc.currentErrCount { + counter.Add(testErr, processName, tc.record.RunID) + } + calls := map[string]int{} timeout := timeout[string, testStatus]{ @@ -192,8 +200,8 @@ func TestProcessTimeout(t *testing.T) { Status: tc.record.Status, } - err := processTimeout(ctx, w, timeout, tc.record, tr, tc.caller(calls).completeFunc, tc.caller(calls).store, tc.caller(calls).updater, "processName", 1) - jtest.RequireNil(t, err) + err := processTimeout(ctx, w, timeout, tc.record, tr, tc.caller(calls).completeFunc, tc.caller(calls).store, tc.caller(calls).updater, processName, 1) + require.Nil(t, err) require.Equal(t, tc.expectedCalls, calls) }) diff --git a/trigger.go b/trigger.go index c446cd9..ef990dc 100644 --- a/trigger.go +++ b/trigger.go @@ -2,11 +2,10 @@ package workflow import ( "context" + "errors" "fmt" "github.com/google/uuid" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" ) func (w *Workflow[Type, Status]) Trigger(ctx context.Context, foreignID string, startingStatus Status, opts ...TriggerOption[Type, Status]) (runID string, err error) { @@ -15,11 +14,13 @@ func (w *Workflow[Type, Status]) Trigger(ctx context.Context, foreignID string, func trigger[Type any, Status StatusType](ctx context.Context, w *Workflow[Type, Status], lookup latestLookup, foreignID string, startingStatus Status, opts ...TriggerOption[Type, Status]) (runID string, err error) { if !w.calledRun { - return "", errors.Wrap(ErrWorkflowNotRunning, "ensure Run() is called before attempting to trigger the workflow") + return "", fmt.Errorf("trigger failed: workflow is not running") } if !w.statusGraph.IsValid(int(startingStatus)) { - return "", errors.Wrap(ErrStatusProvidedNotConfigured, fmt.Sprintf("ensure %v is configured for workflow: %v", startingStatus, w.Name)) + w.logger.maybeDebug(w.ctx, fmt.Sprintf("ensure %v is configured for workflow: %v", startingStatus, w.Name), map[string]string{}) + + return "", fmt.Errorf("trigger failed: status provided is not configured for workflow: %s", startingStatus) } var o triggerOpts[Type, Status] @@ -47,11 +48,7 @@ func trigger[Type any, Status StatusType](ctx context.Context, w *Workflow[Type, // Check that the last run has completed before triggering a new run. if lastRecord.RunState.Valid() && !lastRecord.RunState.Finished() { // Cannot trigger a new run for this foreignID if there is a workflow in progress. - return "", errors.Wrap(ErrWorkflowInProgress, "", j.MKV{ - "run_id": lastRecord.RunID, - "run_state": lastRecord.RunState.String(), - "status": Status(lastRecord.Status).String(), - }) + return "", ErrWorkflowInProgress } uid, err := uuid.NewUUID() diff --git a/trigger_internal_test.go b/trigger_internal_test.go index 97a8e24..52dcc86 100644 --- a/trigger_internal_test.go +++ b/trigger_internal_test.go @@ -2,9 +2,11 @@ package workflow import ( "context" + "errors" + "fmt" "testing" - "github.com/luno/jettison/jtest" + "github.com/stretchr/testify/require" ) func Test_trigger(t *testing.T) { @@ -14,10 +16,11 @@ func Test_trigger(t *testing.T) { }, statusMiddle) w := b.Build(nil, nil, nil, WithDebugMode()) - t.Run("Expected ErrWorkflowNotRunning when Trigger called before Run()", func(t *testing.T) { + t.Run("Expected non-nil error when Trigger called before Run()", func(t *testing.T) { ctx := context.Background() _, err := trigger(ctx, w, nil, "1", statusStart) - jtest.Require(t, ErrWorkflowNotRunning, err) + + require.Equal(t, "trigger failed: workflow is not running", err.Error()) }) t.Run("Expects ErrStatusProvidedNotConfigured when starting status is not configured", func(t *testing.T) { @@ -25,7 +28,7 @@ func Test_trigger(t *testing.T) { w.calledRun = true _, err := trigger(ctx, w, nil, "1", statusEnd) - jtest.Require(t, ErrStatusProvidedNotConfigured, err) + require.Equal(t, fmt.Sprintf("trigger failed: status provided is not configured for workflow: %s", statusEnd), err.Error()) }) t.Run("Expects ErrWorkflowInProgress if a workflow run is already in progress", func(t *testing.T) { @@ -41,6 +44,6 @@ func Test_trigger(t *testing.T) { Status: int(statusMiddle), }, nil }, "1", statusStart) - jtest.Require(t, ErrWorkflowInProgress, err) + require.True(t, errors.Is(err, ErrWorkflowInProgress)) }) } diff --git a/update.go b/update.go index 60f1eb6..7007a20 100644 --- a/update.go +++ b/update.go @@ -2,9 +2,8 @@ package workflow import ( "context" + "fmt" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" "k8s.io/utils/clock" "github.com/luno/workflow/internal/graph" @@ -15,7 +14,7 @@ type ( lookupFunc func(ctx context.Context, id int64) (*Record, error) storeFunc func(ctx context.Context, record *Record, maker OutboxEventDataMaker) error - updater[Type any, Status StatusType] func(ctx context.Context, current Status, next Status, record *Run[Type, Status]) error + updater[Type any, Status StatusType] func(ctx context.Context, current Status, next Status, run *Run[Type, Status]) error ) func newUpdater[Type any, Status StatusType](lookup lookupFunc, store storeFunc, graph *graph.Graph, clock clock.Clock) updater[Type, Status] { @@ -74,9 +73,7 @@ func validateTransition[Status StatusType](current, next Status, graph *graph.Gr // Lookup all available transitions from the current status nodes := graph.Transitions(int(current)) if len(nodes) == 0 { - return errors.New("current status not predefined", j.MKV{ - "current_status": current.String(), - }) + return fmt.Errorf("current status not defined in graph: current=%s", current) } var found bool @@ -90,10 +87,7 @@ func validateTransition[Status StatusType](current, next Status, graph *graph.Gr // If no valid transition matches that of the next status then error. if !found { - return errors.New("invalid transition attempted", j.MKV{ - "current_status": current.String(), - "next_status": next.String(), - }) + return fmt.Errorf("current status not defined in graph: current=%s, next=%s", current, next) } return nil diff --git a/update_internal_test.go b/update_internal_test.go index a7a6b62..0db1232 100644 --- a/update_internal_test.go +++ b/update_internal_test.go @@ -2,11 +2,11 @@ package workflow import ( "context" + "errors" + "fmt" "testing" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -14,6 +14,7 @@ import ( ) func TestUpdater(t *testing.T) { + testErr := errors.New("lookup error") testCases := []struct { name string lookup lookupFunc @@ -66,7 +67,7 @@ func TestUpdater(t *testing.T) { Status: statusMiddle, }, transitions: []graph.Transition{}, - expectedErr: errors.New("current status not predefined"), + expectedErr: fmt.Errorf("current status not defined in graph: current=%s", statusStart), }, { name: "Mark as completed", @@ -94,9 +95,9 @@ func TestUpdater(t *testing.T) { { name: "Return error on lookup", lookup: func(ctx context.Context, id int64) (*Record, error) { - return nil, errors.New("lookup error") + return nil, testErr }, - expectedErr: errors.New("lookup error"), + expectedErr: testErr, }, { name: "Exit early if lookup record status has changed", @@ -129,7 +130,7 @@ func TestUpdater(t *testing.T) { To: int(statusEnd), }, }, - expectedErr: errors.New("invalid transition attempted"), + expectedErr: fmt.Errorf("current status not defined in graph: current=%s, next=%s", statusStart, statusMiddle), }, } for _, tc := range testCases { @@ -144,13 +145,15 @@ func TestUpdater(t *testing.T) { store := func(ctx context.Context, r *Record, maker OutboxEventDataMaker) error { require.Equal(t, tc.expectedRunState, r.RunState) _, err := maker(1) - jtest.RequireNil(t, err) + require.Nil(t, err) return nil } updater := newUpdater[string, testStatus](tc.lookup, store, g, c) err := updater(ctx, tc.current, tc.update.Status, &tc.update) - jtest.Require(t, tc.expectedErr, err) + if err != nil { + require.Equal(t, tc.expectedErr.Error(), err.Error()) + } }) } } diff --git a/visualiser_test.go b/visualiser_test.go index c3af2ab..b1507bb 100644 --- a/visualiser_test.go +++ b/visualiser_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -24,7 +23,7 @@ func TestCreateDiagram(t *testing.T) { wf := b.Build(nil, nil, nil) err := workflow.CreateDiagram(wf, "./testdata/graph-visualisation.md", workflow.LeftToRightDirection) - jtest.RequireNil(t, err) + require.Nil(t, err) } func TestCreateDiagramValidation(t *testing.T) { diff --git a/workflow.go b/workflow.go index 6ee65ee..ddbbcab 100644 --- a/workflow.go +++ b/workflow.go @@ -2,13 +2,12 @@ package workflow import ( "context" + "errors" + "fmt" "io" "sync" "time" - "github.com/luno/jettison/errors" - "github.com/luno/jettison/j" - "github.com/luno/jettison/log" "k8s.io/utils/clock" "github.com/luno/workflow/internal/errorcounter" @@ -28,8 +27,7 @@ type API[Type any, Status StatusType] interface { // was run. Trigger(ctx context.Context, foreignID string, startingStatus Status, opts ...TriggerOption[Type, Status]) (runID string, err error) - // Schedule takes a cron spec and will call Trigger at the specified intervals. Schedule is a blocking call and will - // return ErrWorkflowNotRunning or ErrStatusProvidedNotConfigured to indicate that it cannot begin to schedule. All + // Schedule takes a cron spec and will call Trigger at the specified intervals. Schedule is a blocking call and all // schedule errors will be retried indefinitely. The same options are available for Schedule as they are // for Trigger. Schedule(foreignID string, startingStatus Status, spec string, opts ...ScheduleOption[Type, Status]) error @@ -58,6 +56,7 @@ type Workflow[Type any, Status StatusType] struct { clock clock.Clock calledRun bool once sync.Once + logger *logger eventStreamer EventStreamer recordStore RecordStore @@ -83,10 +82,6 @@ type Workflow[Type any, Status StatusType] struct { // PauseAfterErrCount. The tracking of errors is done in a way where errors need to be unique per process // (consumer / timeout). errorCounter errorcounter.ErrorCounter - - exporterConsumer Consumer - - debugMode bool } func (w *Workflow[Type, Status]) Run(ctx context.Context) { @@ -158,44 +153,76 @@ func (w *Workflow[Type, Status]) Run(ctx context.Context) { }) } -// run is a standardise way of running blocking calls forever with retry such as consumers that need to adhere to role scheduling -func (w *Workflow[Type, Status]) run(role, processName string, process func(ctx context.Context) error, errBackOff time.Duration) { +// run is a standardise way of running blocking calls with a built-in retry mechanism. +func (w *Workflow[Type, Status]) run( + role string, + processName string, + process func(ctx context.Context) error, + errBackOff time.Duration, +) { w.updateState(processName, StateIdle) defer w.updateState(processName, StateShutdown) for { - err := runOnce(w, role, processName, process, errBackOff) + err := runOnce( + w.ctx, + w.Name, + role, + processName, + w.updateState, + w.scheduler.Await, + process, + w.logger, + w.clock, + errBackOff, + ) if err != nil { - if w.debugMode { - log.Info(w.ctx, "shutting down process", j.MKV{ - "role": role, - "process_name": processName, - }) - } + w.logger.maybeDebug(w.ctx, "shutting down process", map[string]string{ + "role": role, + "process_name": processName, + }) + return } } } -func runOnce[Type any, Status StatusType](w *Workflow[Type, Status], role, processName string, process func(ctx context.Context) error, errBackOff time.Duration) error { - w.updateState(processName, StateIdle) +type ( + updateStateFn func(processName string, s State) + awaitRoleFn func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) +) + +func runOnce( + ctx context.Context, + workflowName string, + role string, + processName string, + updateState updateStateFn, + awaitRole awaitRoleFn, + process func(ctx context.Context) error, + logger *logger, + clock clock.Clock, + errBackOff time.Duration, +) error { + if ctx.Err() != nil { + return ctx.Err() + } - ctx, cancel, err := w.scheduler.Await(w.ctx, role) - if errors.IsAny(err, context.Canceled) { + updateState(processName, StateIdle) + + ctx, cancel, err := awaitRole(ctx, role) + if errors.Is(err, context.Canceled) { // Exit cleanly if error returned is cancellation of context return err } else if err != nil { - log.Error(ctx, errors.Wrap(err, "error awaiting role", j.MKV{ - "role": role, - "process_name": processName, - })) + logger.Error(ctx, fmt.Errorf("run error [role=%v]: %v", role, err)) // Return nil to try again return nil } defer cancel() - w.updateState(processName, StateRunning) + updateState(processName, StateRunning) err = process(ctx) if errors.Is(err, context.Canceled) { @@ -203,15 +230,14 @@ func runOnce[Type any, Status StatusType](w *Workflow[Type, Status], role, proce // and if the parent context was cancelled then that will exit safely. return nil } else if err != nil { - log.Error(ctx, errors.Wrap(err, "process error", j.MKV{ - "role": role, - })) - metrics.ProcessErrors.WithLabelValues(w.Name, processName).Inc() + logger.Error(ctx, fmt.Errorf("run error [role=%v]: %v", role, err)) + metrics.ProcessErrors.WithLabelValues(workflowName, processName).Inc() + timer := clock.NewTimer(errBackOff) select { case <-ctx.Done(): return nil - case <-time.After(errBackOff): + case <-timer.C(): // Return nil to try again return nil } diff --git a/workflow_internal_test.go b/workflow_internal_test.go new file mode 100644 index 0000000..3b4a4f6 --- /dev/null +++ b/workflow_internal_test.go @@ -0,0 +1,181 @@ +package workflow + +import ( + "bytes" + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "k8s.io/utils/clock" + clock_testing "k8s.io/utils/clock/testing" + + internal_logger "github.com/luno/workflow/internal/logger" +) + +func Test_runOnce(t *testing.T) { + ctx := context.Background() + + t.Run("Returns non-nil error (context.Canceled) when parent ctx is cancelled", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + cancel() + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + nil, + nil, + nil, + nil, + clock_testing.NewFakeClock(time.Now()), + time.Minute, + ) + require.True(t, errors.Is(err, context.Canceled)) + }) + + t.Run("Returns awaitRole's context cancellation", func(t *testing.T) { + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + func(processName string, s State) {}, + func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) { + return nil, nil, context.Canceled + }, + func(ctx context.Context) error { + return nil + }, + nil, + clock_testing.NewFakeClock(time.Now()), + time.Minute, + ) + require.True(t, errors.Is(err, context.Canceled)) + }) + + t.Run("Retry awaitRole error that is not context.Canceled", func(t *testing.T) { + testErr := errors.New("test error") + buf := bytes.NewBuffer([]byte{}) + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + func(processName string, s State) {}, + func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) { + return nil, nil, testErr + }, + func(ctx context.Context) error { + return nil + }, + &logger{ + debugMode: false, + inner: internal_logger.New(buf), + }, + clock.RealClock{}, + time.Minute, + ) + require.Nil(t, err) + require.Contains(t, buf.String(), `"msg":"run error [role=role-1]: test error"`) + }) + + t.Run("Cancelled parent context during process execution retries and exits with context.Canceled ", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + func(processName string, s State) {}, + func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) { + ctx, cancel := context.WithCancel(ctx) + return ctx, cancel, nil + }, + func(ctx context.Context) error { + // Cancel parent context and return context error + cancel() + return ctx.Err() + }, + nil, + clock_testing.NewFakeClock(time.Now()), + time.Minute, + ) + // If the err is nil then it will be retried + require.Nil(t, err) + + // Context has been cancelled previously in the last runOnce so we expect + // this to immediately return context.Canceled and need any parameters. + err = runOnce( + ctx, + "", + "", + "", + nil, + nil, + nil, + nil, + nil, + time.Minute, + ) + require.True(t, errors.Is(err, context.Canceled)) + }) + + t.Run("Retry process error that is not context.Canceled", func(t *testing.T) { + testErr := errors.New("test error") + buf := bytes.NewBuffer([]byte{}) + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + func(processName string, s State) {}, + func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) { + ctx, cancel := context.WithCancel(ctx) + return ctx, cancel, nil + }, + func(ctx context.Context) error { + return testErr + }, + &logger{ + debugMode: false, + inner: internal_logger.New(buf), + }, + clock.RealClock{}, + time.Millisecond, + ) + + require.Nil(t, err) + require.Contains(t, buf.String(), `"msg":"run error [role=role-1]: test error`) + }) + + t.Run("Updates process state", func(t *testing.T) { + var stateChanges []string + err := runOnce( + ctx, + "workflow-1", + "role-1", + "process-1", + func(processName string, s State) { + stateChanges = append(stateChanges, s.String()) + }, + func(ctx context.Context, role string) (context.Context, context.CancelFunc, error) { + ctx, cancel := context.WithCancel(ctx) + return ctx, cancel, nil + }, + func(ctx context.Context) error { + return nil + }, + nil, + clock_testing.NewFakeClock(time.Now()), + time.Minute, + ) + require.Nil(t, err) + + expected := []string{StateIdle.String(), StateRunning.String()} + require.Equal(t, expected, stateChanges) + }) +} diff --git a/workflow_test.go b/workflow_test.go index 8d1bc57..4010feb 100644 --- a/workflow_test.go +++ b/workflow_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" clock_testing "k8s.io/utils/clock/testing" @@ -134,7 +133,7 @@ func TestWorkflowAcceptanceTest(t *testing.T) { } runID, err := wf.Trigger(ctx, fid, StatusInitiated, workflow.WithInitialValue[MyType, status](&mt)) - jtest.RequireNil(t, err) + require.Nil(t, err) // Once in the correct status, trigger third party callbacks workflow.TriggerCallbackOn(t, wf, fid, runID, StatusEmailConfirmationSent, ExternalEmailVerified{ @@ -156,15 +155,15 @@ func TestWorkflowAcceptanceTest(t *testing.T) { clock.Step(time.Hour) _, err = wf.Await(ctx, fid, runID, StatusCompleted) - jtest.RequireNil(t, err) + require.Nil(t, err) r, err := recordStore.Latest(ctx, "user sign up", fid) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, int(expectedFinalStatus), r.Status) var actual MyType err = workflow.Unmarshal(r.Object, &actual) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, expectedUserID, actual.UserID) require.Equal(t, strconv.FormatInt(expectedUserID, 10), actual.ForeignID()) @@ -223,7 +222,9 @@ func benchmarkWorkflow(b *testing.B, numberOfSteps int) { } for range b.N { _, err := wf.Trigger(ctx, fid, 0, workflow.WithInitialValue[MyType, status](&mt)) - jtest.RequireNil(b, err) + if err != nil { + b.Fatal(err) + } workflow.Require(b, wf, fid, status(numberOfSteps), MyType{ UserID: expectedUserID, @@ -265,7 +266,7 @@ func TestTimeout(t *testing.T) { start := time.Now() runID, err := wf.Trigger(ctx, "example", StatusInitiated) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.AwaitTimeoutInsert(t, wf, "example", runID, StatusProfileCreated) @@ -273,7 +274,7 @@ func TestTimeout(t *testing.T) { clock.Step(time.Hour) _, err = wf.Await(ctx, "example", runID, StatusCompleted) - jtest.RequireNil(t, err) + require.Nil(t, err) end := time.Now() @@ -390,10 +391,10 @@ func TestWorkflow_ErrWorkflowNotRunning(t *testing.T) { }) _, err := wf.Trigger(ctx, "andrew", StatusStart) - jtest.Require(t, workflow.ErrWorkflowNotRunning, err) + require.Equal(t, "trigger failed: workflow is not running", err.Error()) err = wf.Schedule("andrew", StatusStart, "@monthly") - jtest.Require(t, workflow.ErrWorkflowNotRunning, err) + require.Equal(t, "schedule failed: workflow is not running", err.Error()) } func TestWorkflow_TestingRequire(t *testing.T) { @@ -425,7 +426,7 @@ func TestWorkflow_TestingRequire(t *testing.T) { foreignID := "andrew" _, err := wf.Trigger(ctx, foreignID, StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) expected := MyType{ Email: "andrew@workflow.com", @@ -478,7 +479,7 @@ func TestTimeTimerFunc(t *testing.T) { t.Cleanup(wf.Stop) runID, err := wf.Trigger(ctx, "Andrew Wormald", StatusStart) - jtest.RequireNil(t, err) + require.Nil(t, err) workflow.AwaitTimeoutInsert(t, wf, "Andrew Wormald", runID, StatusStart) @@ -592,12 +593,12 @@ func TestStepConsumerLag(t *testing.T) { _, err := wf.Trigger(ctx, foreignID, StatusStart, workflow.WithInitialValue[TimeWatcher, status](&TimeWatcher{ StartTime: clock.Now(), })) - jtest.RequireNil(t, err) + require.Nil(t, err) time.Sleep(time.Second) latest, err := recordStore.Latest(ctx, wf.Name, foreignID) - jtest.RequireNil(t, err) + require.Nil(t, err) // Ensure that the record has not been consumer or updated require.Equal(t, int64(1), latest.ID) diff --git a/workflowpb/util.go b/workflowpb/util.go index 7a594de..a5f1e26 100644 --- a/workflowpb/util.go +++ b/workflowpb/util.go @@ -1,7 +1,6 @@ package workflowpb import ( - "github.com/luno/jettison/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" @@ -11,7 +10,7 @@ import ( func ProtoMarshal(r *workflow.Record) ([]byte, error) { pb, err := proto.Marshal(ToProto(r)) if err != nil { - return nil, errors.Wrap(err, "failed to proto marshal record") + return nil, err } return pb, nil @@ -35,7 +34,7 @@ func UnmarshalRecord(b []byte) (*workflow.Record, error) { var wpb Record err := proto.Unmarshal(b, &wpb) if err != nil { - return nil, errors.Wrap(err, "failed to proto marshal record") + return nil, err } return &workflow.Record{ diff --git a/workflowpb/util_test.go b/workflowpb/util_test.go index 7948834..5529965 100644 --- a/workflowpb/util_test.go +++ b/workflowpb/util_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/luno/jettison/jtest" "github.com/stretchr/testify/require" "github.com/luno/workflow" @@ -26,10 +25,10 @@ func TestProtoMarshalAndUnmarshal(t *testing.T) { } protoBytes, err := workflowpb.ProtoMarshal(&wireRecord) - jtest.RequireNil(t, err) + require.Nil(t, err) deserialised, err := workflowpb.UnmarshalRecord(protoBytes) - jtest.RequireNil(t, err) + require.Nil(t, err) require.Equal(t, wireRecord, *deserialised) }