Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/workertest/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ func (env *TestEnvironment) NexusEndpointName() string {
return env.nexusEndpointName
}

func (env *TestEnvironment) Namespace() string {
return testNamespace
}

func SetupTestEnvironment(t *testing.T, opts ...TestEnvOption) *TestEnvironment {
cfg := testEnvConfig{
executorTimeout: defaultTestRunTimeout,
Expand Down
16 changes: 16 additions & 0 deletions internal/workertest/historyrequire.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ func mapIsSuperset(actual, expected map[string]any) bool {
return true
}

// looselyEqual is used by [mapIsSuperset] and in turn for
// partial/full history matching
func looselyEqual(x, y any) bool {
switch x := x.(type) {
case float64:
Expand All @@ -278,6 +280,20 @@ func looselyEqual(x, y any) bool {
return mapIsSuperset(x, yMap)
}
return false
case []any:
Comment thread
mavemuri marked this conversation as resolved.
// Match the unordered expected list so a spec can
// assert only what it knows. Eg, when asserting links, if runID is
// not known, it should be possible to assert on just WID+NS
yList, ok := y.([]any)
if !ok || len(yList) != len(x) {
return false
}
for i, yv := range yList {
if !looselyEqual(x[i], yv) {
return false
}
Comment on lines +291 to +294

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about matching elements in any order? We only have a single link right now; but I can see this fail if there are 2 links and their order isn't deterministic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not sure when that could happen - multiple links on same event in non-deterministic order. Maybe server should always return in some order - chronological or alphabetical? But not clear on this- could we revisit when its required?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine with me; can we tighten the comment there?

// Match the unordered expected list so a spec can
// assert only what it knows. Eg, when asserting links, if runID is
// not known, it should be possible to assert on just WID+NS

I don't quite understand what it's saying tbh. Esp. since they are not "unordered".

I'd omit it or add a shorter comment maybe on if !looselyEqual(x[i], yv) { to explain that this recurses into list elements (if that's what it means to say). Or drop the comment.

}
return true
}
return reflect.DeepEqual(x, y)
}
Expand Down
112 changes: 98 additions & 14 deletions loadgen/kitchen_sink_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/temporalio/omes/clioptions"
. "github.com/temporalio/omes/internal/workertest"
Expand All @@ -18,13 +19,11 @@ import (
"go.temporal.io/api/enums/v1"
"go.temporal.io/api/history/v1"
"go.temporal.io/api/workflowservice/v1"
"go.temporal.io/sdk/client"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/emptypb"
)

const namespace = "default"

var (
sdks = []clioptions.Language{
clioptions.LangGo,
Expand Down Expand Up @@ -59,6 +58,14 @@ var (
clioptions.LangTypeScript: "dostandaloneactivityoperatorcommands is not supported",
clioptions.LangDotNet: "dostandaloneactivityoperatorcommands is not supported",
}

nexusQueryUnsupportedSDKs = map[clioptions.Language]string{
clioptions.LangPython: "query-workflow",
clioptions.LangJava: "executenexusoperation is not supported",
clioptions.LangRuby: "executenexusoperation is not supported",
clioptions.LangTypeScript: "executenexusoperation is not supported",
clioptions.LangDotNet: "executenexusoperation is not supported",
}
)

type testCase struct {
Expand All @@ -67,6 +74,17 @@ type testCase struct {
historyMatcher HistoryMatcher
expectedUnsupportedErrs map[clioptions.Language]string
expectedWorkflowError string
populateFn func(t *testing.T) testCase

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't love it. I think maybe this was the wrong direction; instead we should isolate the SDK runs by using separate namespaces. I noodled a bit one it and came up with this: #462

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, per-SDK ns is cleaner - thanks for raising the pr

}

// populate creates per-run test data for cases that require it.
func (tc testCase) populate(t *testing.T) testCase {
t.Helper() // Keeps error lines pointing to the actual test table

if tc.populateFn == nil {
return tc
}
return tc.populateFn(t)
}

// TestKitchenSink tests specific kitchensink features across SDKs.
Expand Down Expand Up @@ -939,6 +957,10 @@ func TestKitchenSink(t *testing.T) {
NexusOperationCompleted`),
expectedUnsupportedErrs: nexusUnsupportedSDKs,
},
{
name: "NexusOperation/QueryWorkflow",
populateFn: populateQueryWorkflowTestCase,
},
{
name: "NexusOperation/Async",
testInput: &TestInput{
Expand Down Expand Up @@ -1129,13 +1151,6 @@ func TestKitchenSink(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

// Ensure the workflow completes by appending a return action at the end.
input := tc.testInput
if input.WorkflowInput == nil {
input.WorkflowInput = &WorkflowInput{}
}
input.WorkflowInput.InitialActions = append(input.WorkflowInput.InitialActions, ListActionSet(NewEmptyReturnResultAction())...)

for _, sdk := range sdks {
if onlySDK != "" && string(sdk) != onlySDK {
continue // not using t.Skip as it's too noisy
Expand All @@ -1149,6 +1164,62 @@ func TestKitchenSink(t *testing.T) {
}
}

func populateQueryWorkflowTestCase(t *testing.T) testCase {
t.Helper()

handlerWorkflowID := fmt.Sprintf("nexus-query-target-%s", uuid.NewString())
return testCase{
name: "NexusOperation/QueryWorkflow",
testInput: &TestInput{
WorkflowInput: &WorkflowInput{
InitialActions: ListActionSet(
// Start a Nexus operation and wait for its signal as a scaffold to
// query a running workflow.
&Action{
Variant: &Action_NexusOperation{
NexusOperation: &ExecuteNexusOperation{
Operation: "echo-async",
HandlerWorkflowId: handlerWorkflowID,
HandlerWorkflowIdConflictPolicy: enums.WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING,
BeforeActions: ListActionSet(
NewSetWorkflowStateAction("query-result", "query successful"),
),
WaitForSignal: true,
AwaitableChoice: &AwaitableChoice{
Condition: &AwaitableChoice_WaitStarted{
WaitStarted: &emptypb.Empty{},
},
},
},
},
},
&Action{
Variant: &Action_NexusOperation{
NexusOperation: &ExecuteNexusOperation{
Operation: "query-workflow",
Input: QueryWorkflowNexusInput(QueryWorkflowTarget{
WorkflowID: handlerWorkflowID,
}),
ExpectedOutput: "query successful",
AwaitableChoice: &AwaitableChoice{
Condition: &AwaitableChoice_WaitFinish{
WaitFinish: &emptypb.Empty{},
},
},
},
},
},
),
},
},
historyMatcher: PartialHistoryMatcher(fmt.Sprintf(`
NexusOperationScheduled {"operation":"query-workflow"}
NexusOperationCompleted {"links":[{"workflow":{"workflowId":%q,"reason":"Query processed"}}]}`,
handlerWorkflowID)),
expectedUnsupportedErrs: nexusQueryUnsupportedSDKs,
}
}

func standaloneActivityOperatorCommandsTestCase(
name string,
commandType DoStandaloneActivityOperatorCommands_CommandType,
Expand Down Expand Up @@ -1198,6 +1269,8 @@ func testForSDK(
env *TestEnvironment,
workflowTimeout time.Duration,
) {
tc = tc.populate(t)

// Use mutex to ensure only one Java test runs at a time/a Gradle limitation.
if sdk == clioptions.LangJava {
javaMutex.Lock()
Expand All @@ -1220,8 +1293,18 @@ func testForSDK(
// worker picks them up.
runTaskQueue := TaskQueueForRun(scenarioInfo.RunID)

// PrepareTestInput fills the per-run endpoint and task queue into the input in
// place, so work on a copy before making further per-run changes.
testInput := proto.Clone(tc.testInput).(*TestInput)

// Ensure the workflow completes by appending a return action at the end.
if testInput.WorkflowInput == nil {
testInput.WorkflowInput = &WorkflowInput{}
}
testInput.WorkflowInput.InitialActions = append(testInput.WorkflowInput.InitialActions, ListActionSet(NewEmptyReturnResultAction())...)

executor := &KitchenSinkExecutor{
TestInput: tc.testInput,
TestInput: testInput,
PrepareTestInput: func(_ context.Context, _ ScenarioInfo, input *TestInput) error {
if input.WorkflowInput != nil {
for _, actionSet := range input.WorkflowInput.InitialActions {
Expand Down Expand Up @@ -1298,7 +1381,7 @@ func testSupportedFeature(
_, execErr := env.RunExecutorTest(t, testExecutor, scenarioInfo, sdk)

taskQueueName := TaskQueueForRun(scenarioInfo.RunID)
historyEvents, historyErr := getWorkflowHistory(t, taskQueueName, env.TemporalClient())
historyEvents, historyErr := getWorkflowHistory(t, taskQueueName, env)
if execErr != nil {
if len(historyEvents) > 0 {
t.Logf("History events for debugging:")
Expand Down Expand Up @@ -1340,10 +1423,11 @@ func (w *kitchenSinkTestWrapper) Run(ctx context.Context, info ScenarioInfo) err
return w.executor.Run(ctx, info)
}

func getWorkflowHistory(t *testing.T, taskQueueName string, temporalClient client.Client) ([]*history.HistoryEvent, error) {
func getWorkflowHistory(t *testing.T, taskQueueName string, env *TestEnvironment) ([]*history.HistoryEvent, error) {
temporalClient := env.TemporalClient()
executions, err := temporalClient.ListWorkflow(t.Context(),
&workflowservice.ListWorkflowExecutionsRequest{
Namespace: namespace,
Namespace: env.Namespace(),
Query: fmt.Sprintf("TaskQueue = '%s' AND WorkflowType = 'kitchenSink'", taskQueueName),
})
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions loadgen/kitchensink/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kitchensink

import (
"encoding/json"
"fmt"
"math/rand"
"time"
Expand All @@ -13,6 +14,21 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)

// QueryWorkflowTarget identifies the workflow that the "query-workflow" Nexus operation queries.
// Only WorkflowID is required

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
// Only WorkflowID is required
// WorkflowID is required.

type QueryWorkflowTarget struct {
WorkflowID string `json:"workflow_id"`
}

// QueryWorkflowNexusInput encodes a target as the input of a "query-workflow" Nexus operation.
func QueryWorkflowNexusInput(target QueryWorkflowTarget) string {
encoded, err := json.Marshal(target)
if err != nil {
panic(err)
}
return string(encoded)
}

// Using human-readable JSON encoding for payloads to aid with debugging.
var jsonPayloadConverter = converter.NewProtoJSONPayloadConverter()

Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ruby = "1.6.0"
typescript = "1.22.0"

[_.server]
ref = "abcb94873061dd55e80745cbcc84037abdf22d53"
ref = "042505361519608454d4a016b3d0971c81c724c8"

[tasks."sync-sdk:go"]
description = "Sync Go SDK dependencies to the version in mise.toml"
Expand Down
7 changes: 6 additions & 1 deletion workers/go/apps/lambda/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ func configureLambdaWorker(opts *lambdaworker.Options) error {
ebbFlowActivities := ebbandflow.Activities{}

service := nexus.NewService(kitchensink.KitchenSinkServiceName)
for _, op := range []nexus.RegisterableOperation{kitchensink.EchoSyncOperation, kitchensink.EchoAsyncOperation, kitchensink.StandaloneActivityNexusOperation} {
for _, op := range []nexus.RegisterableOperation{
kitchensink.EchoSyncOperation,
kitchensink.EchoAsyncOperation,
kitchensink.StandaloneActivityNexusOperation,
kitchensink.QueryWorkflowOperation,
} {
if err := service.Register(op); err != nil {
return fmt.Errorf("failed to register nexus operation: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions workers/go/apps/worker/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func buildWorker(client sdkclient.Client, context harness.WorkerContext) sdkwork
kitchensink.EchoSyncOperation,
kitchensink.EchoAsyncOperation,
kitchensink.StandaloneActivityNexusOperation,
kitchensink.QueryWorkflowOperation,
} {
if err := service.Register(op); err != nil {
panic(err)
Expand Down
Loading
Loading