From ec7971dd088e8249cee98d0ce1c147c6522959e1 Mon Sep 17 00:00:00 2001 From: feedmeapples Date: Tue, 4 Apr 2023 13:30:45 -0400 Subject: [PATCH 1/2] Create e2e suite and replay test --- go.mod | 1 + go.sum | 2 + tests/e2e_test.go | 116 ++++++++++++++++++ tests/workflow_test.go | 53 ++++++++ tests/workflows/helloworld/helloworld.go | 40 ++++++ tests/workflows/helloworld/helloworld_test.go | 39 ++++++ 6 files changed, 251 insertions(+) create mode 100644 tests/e2e_test.go create mode 100644 tests/workflow_test.go create mode 100644 tests/workflows/helloworld/helloworld.go create mode 100644 tests/workflows/helloworld/helloworld_test.go diff --git a/go.mod b/go.mod index 7f472989..43d54555 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/urfave/cli/v2 v2.23.6 go.temporal.io/api v1.18.2-0.20230324225508-f2c7ab685b44 go.temporal.io/sdk v1.21.1 + go.temporal.io/sdk/contrib/tools/workflowcheck v0.0.0-20230328164709-88a40de39c33 go.temporal.io/server v1.20.1 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 diff --git a/go.sum b/go.sum index 78fc8e2e..66ceb3da 100644 --- a/go.sum +++ b/go.sum @@ -1132,6 +1132,8 @@ go.temporal.io/api v1.18.2-0.20230324225508-f2c7ab685b44 h1:ilEY3HPpO6UwiwopVMuX go.temporal.io/api v1.18.2-0.20230324225508-f2c7ab685b44/go.mod h1:VAc6LF2ckIwAbMEMv+eo+3yw4SjIzwosukjiwXajCJc= go.temporal.io/sdk v1.21.1 h1:SJCzSsZLBsFiHniJ+E7Yy74pcAs1lg7NbFnsUJ4ggIM= go.temporal.io/sdk v1.21.1/go.mod h1:Pq3Mp7p0lWNFM+YS2guBy8V/lJySh329AcyS+Wj/Wmo= +go.temporal.io/sdk/contrib/tools/workflowcheck v0.0.0-20230328164709-88a40de39c33 h1:tpDvC3HKzoPGmYZT7LBkYtBWrbZa8GNiLR2LG5iG5sw= +go.temporal.io/sdk/contrib/tools/workflowcheck v0.0.0-20230328164709-88a40de39c33/go.mod h1:CW0zVy7oLeWxBo3wG5bMU2dy4xaprM2net3/DkBzruw= go.temporal.io/server v1.20.1 h1:vhI7XLQJP0TbYWIJTHzliCaYrekxY3XUhtg0MUSxaXs= go.temporal.io/server v1.20.1/go.mod h1:0XqeAbOIa+6J1qvne7yEbXKfchw3sIkCaQ16Qo6ZeiU= go.temporal.io/version v0.3.0 h1:dMrei9l9NyHt8nG6EB8vAwDLLTwx2SvRyucCSumAiig= diff --git a/tests/e2e_test.go b/tests/e2e_test.go new file mode 100644 index 00000000..7d49ce1a --- /dev/null +++ b/tests/e2e_test.go @@ -0,0 +1,116 @@ +package tests + +import ( + "bytes" + "context" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/temporalio/cli/app" + "github.com/urfave/cli/v2" + "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/testsuite" + "go.temporal.io/sdk/worker" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +type ( + e2eSuite struct { + suite.Suite + app *cli.App + ts *testsuite.DevServer + workers []worker.Worker + defaultWorkerOptions worker.Options + writer *MemWriter + } +) + +func TestClientIntegrationSuite(t *testing.T) { + suite.Run(t, new(e2eSuite)) +} + +func (s *e2eSuite) SetupSuite() { + s.app = app.BuildApp() + server, err := testsuite.StartDevServer(context.Background(), testsuite.DevServerOptions{}) + s.NoError(err) + s.ts = server +} + +func (s *e2eSuite) TearDownSuite() { +} + +func (s *e2eSuite) SetupTest() { + app.SetFactory(&clientFactory{ + frontendClient: nil, + operatorClient: nil, + sdkClient: s.ts.Client(), + }) + s.writer = &MemWriter{} + s.app.Writer = s.writer +} + +func (s *e2eSuite) TearDownTest() { + s.ts.Stop() +} + +func (s *e2eSuite) NewWorker(taskQueue string, registerFunc func(registry worker.Registry)) worker.Worker { + w := worker.New(s.ts.Client(), taskQueue, s.defaultWorkerOptions) + registerFunc(w) + s.workers = append(s.workers, w) + + err := w.Start() + s.NoError(err) + + return w +} + +type clientFactory struct { + frontendClient workflowservice.WorkflowServiceClient + operatorClient operatorservice.OperatorServiceClient + sdkClient client.Client +} + +func (m *clientFactory) FrontendClient(c *cli.Context) workflowservice.WorkflowServiceClient { + return m.frontendClient +} + +func (m *clientFactory) OperatorClient(c *cli.Context) operatorservice.OperatorServiceClient { + return m.operatorClient +} + +func (m *clientFactory) SDKClient(c *cli.Context, namespace string) client.Client { + return m.sdkClient +} + +func (m *clientFactory) HealthClient(_ *cli.Context) healthpb.HealthClient { + panic("HealthClient mock is not supported") +} + +// MemWriter is an io.Writer implementation that stores the written content. +type MemWriter struct { + content bytes.Buffer +} + +func (tlw *MemWriter) Write(p []byte) (n int, err error) { + return tlw.content.Write(p) +} + +func (tlw *MemWriter) GetContent() string { + return tlw.content.String() +} + +func TestMemWriter(t *testing.T) { + tlw := &MemWriter{} + fmt.Fprintln(tlw, "This message is written to the TestLogWriter.") + + content := tlw.GetContent() + expected := "This message is written to the TestLogWriter." + + if !strings.Contains(content, expected) { + t.Errorf("Expected log content to contain '%s', but it doesn't. Content: '%s'", expected, content) + } +} diff --git a/tests/workflow_test.go b/tests/workflow_test.go new file mode 100644 index 00000000..0909c3bb --- /dev/null +++ b/tests/workflow_test.go @@ -0,0 +1,53 @@ +package tests + +import ( + "context" + "io/ioutil" + "os" + + "github.com/pborman/uuid" + "github.com/temporalio/cli/tests/workflows/helloworld" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" +) + +const ( + testTq = "test-queue" +) + +func (s *e2eSuite) TestWorkflowShow_ReplayableHistory() { + c := s.ts.Client() + + s.NewWorker(testTq, func(r worker.Registry) { + r.RegisterWorkflow(helloworld.Workflow) + r.RegisterActivity(helloworld.Activity) + }) + + wfr, err := c.ExecuteWorkflow( + context.Background(), + client.StartWorkflowOptions{TaskQueue: testTq}, + helloworld.Workflow, + "world", + ) + s.NoError(err) + + var result string + err = wfr.Get(context.Background(), &result) + s.NoError(err) + + // show history + err = s.app.Run([]string{"", "workflow", "show", "--workflow-id", wfr.GetID(), "--run-id", wfr.GetRunID(), "--output", "json"}) + s.NoError(err) + + // save history to file + historyFile := uuid.New() + ".json" + logs := s.writer.GetContent() + err = ioutil.WriteFile(historyFile, []byte(logs), 0644) + s.NoError(err) + defer os.Remove(historyFile) + + replayer := worker.NewWorkflowReplayer() + replayer.RegisterWorkflow(helloworld.Workflow) + err = replayer.ReplayWorkflowHistoryFromJSONFile(nil, historyFile) + s.NoError(err) +} diff --git a/tests/workflows/helloworld/helloworld.go b/tests/workflows/helloworld/helloworld.go new file mode 100644 index 00000000..36905558 --- /dev/null +++ b/tests/workflows/helloworld/helloworld.go @@ -0,0 +1,40 @@ +package helloworld + +import ( + "context" + "time" + + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/workflow" + + // TODO(cretz): Remove when tagged + _ "go.temporal.io/sdk/contrib/tools/workflowcheck/determinism" +) + +// Workflow is a Hello World workflow definition. +func Workflow(ctx workflow.Context, name string) (string, error) { + ao := workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + } + ctx = workflow.WithActivityOptions(ctx, ao) + + logger := workflow.GetLogger(ctx) + logger.Info("HelloWorld workflow started", "name", name) + + var result string + err := workflow.ExecuteActivity(ctx, Activity, name).Get(ctx, &result) + if err != nil { + logger.Error("Activity failed.", "Error", err) + return "", err + } + + logger.Info("HelloWorld workflow completed.", "result", result) + + return result, nil +} + +func Activity(ctx context.Context, name string) (string, error) { + logger := activity.GetLogger(ctx) + logger.Info("Activity", "name", name) + return "Hello " + name + "!", nil +} diff --git a/tests/workflows/helloworld/helloworld_test.go b/tests/workflows/helloworld/helloworld_test.go new file mode 100644 index 00000000..d4b95694 --- /dev/null +++ b/tests/workflows/helloworld/helloworld_test.go @@ -0,0 +1,39 @@ +package helloworld + +import ( + "testing" + + "github.com/stretchr/testify/mock" + + "github.com/stretchr/testify/require" + "go.temporal.io/sdk/testsuite" +) + +func Test_Workflow(t *testing.T) { + testSuite := &testsuite.WorkflowTestSuite{} + env := testSuite.NewTestWorkflowEnvironment() + + // Mock activity implementation + env.OnActivity(Activity, mock.Anything, "Temporal").Return("Hello Temporal!", nil) + + env.ExecuteWorkflow(Workflow, "Temporal") + + require.True(t, env.IsWorkflowCompleted()) + require.NoError(t, env.GetWorkflowError()) + var result string + require.NoError(t, env.GetWorkflowResult(&result)) + require.Equal(t, "Hello Temporal!", result) +} + +func Test_Activity(t *testing.T) { + testSuite := &testsuite.WorkflowTestSuite{} + env := testSuite.NewTestActivityEnvironment() + env.RegisterActivity(Activity) + + val, err := env.ExecuteActivity(Activity, "World") + require.NoError(t, err) + + var res string + require.NoError(t, val.Get(&res)) + require.Equal(t, "Hello World!", res) +} From f90943591d5e097be8526da3498313af40fa6fbb Mon Sep 17 00:00:00 2001 From: feedmeapples Date: Tue, 4 Apr 2023 13:31:36 -0400 Subject: [PATCH 2/2] . --- tests/workflows/helloworld/helloworld_test.go | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 tests/workflows/helloworld/helloworld_test.go diff --git a/tests/workflows/helloworld/helloworld_test.go b/tests/workflows/helloworld/helloworld_test.go deleted file mode 100644 index d4b95694..00000000 --- a/tests/workflows/helloworld/helloworld_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package helloworld - -import ( - "testing" - - "github.com/stretchr/testify/mock" - - "github.com/stretchr/testify/require" - "go.temporal.io/sdk/testsuite" -) - -func Test_Workflow(t *testing.T) { - testSuite := &testsuite.WorkflowTestSuite{} - env := testSuite.NewTestWorkflowEnvironment() - - // Mock activity implementation - env.OnActivity(Activity, mock.Anything, "Temporal").Return("Hello Temporal!", nil) - - env.ExecuteWorkflow(Workflow, "Temporal") - - require.True(t, env.IsWorkflowCompleted()) - require.NoError(t, env.GetWorkflowError()) - var result string - require.NoError(t, env.GetWorkflowResult(&result)) - require.Equal(t, "Hello Temporal!", result) -} - -func Test_Activity(t *testing.T) { - testSuite := &testsuite.WorkflowTestSuite{} - env := testSuite.NewTestActivityEnvironment() - env.RegisterActivity(Activity) - - val, err := env.ExecuteActivity(Activity, "World") - require.NoError(t, err) - - var res string - require.NoError(t, val.Get(&res)) - require.Equal(t, "Hello World!", res) -}