|
6 | 6 | "fmt" |
7 | 7 | "reflect" |
8 | 8 | "runtime" |
| 9 | + "strings" |
9 | 10 | "sync" |
10 | 11 | "sync/atomic" |
11 | 12 | "testing" |
@@ -357,6 +358,177 @@ func TestWorkflowsRegistration(t *testing.T) { |
357 | 358 | }() |
358 | 359 | RegisterWorkflow(freshCtx, simpleWorkflow) |
359 | 360 | }) |
| 361 | + |
| 362 | + t.Run("SafeGobRegister", func(t *testing.T) { |
| 363 | + // Create a fresh DBOS context for this test |
| 364 | + freshCtx := setupDBOS(t, false, true) // Don't reset DB but do check for leaks |
| 365 | + |
| 366 | + // Test 1: Basic type vs pointer conflicts |
| 367 | + type TestType struct { |
| 368 | + Value string |
| 369 | + } |
| 370 | + |
| 371 | + // Register workflows that use the same type to trigger potential gob conflicts |
| 372 | + // The safeGobRegister calls within RegisterWorkflow should handle the conflicts |
| 373 | + workflow1 := func(ctx DBOSContext, input TestType) (TestType, error) { |
| 374 | + return input, nil |
| 375 | + } |
| 376 | + workflow2 := func(ctx DBOSContext, input *TestType) (*TestType, error) { |
| 377 | + return input, nil |
| 378 | + } |
| 379 | + |
| 380 | + // Both registrations should succeed despite using conflicting types (T and *T) |
| 381 | + RegisterWorkflow(freshCtx, workflow1) |
| 382 | + RegisterWorkflow(freshCtx, workflow2) |
| 383 | + |
| 384 | + // Test 2: Multiple workflows with the same types (duplicate registrations) |
| 385 | + workflow3 := func(ctx DBOSContext, input TestType) (TestType, error) { |
| 386 | + return TestType{Value: input.Value + "-modified"}, nil |
| 387 | + } |
| 388 | + workflow4 := func(ctx DBOSContext, input TestType) (TestType, error) { |
| 389 | + return TestType{Value: input.Value + "-another"}, nil |
| 390 | + } |
| 391 | + |
| 392 | + // These should succeed even though TestType is already registered |
| 393 | + RegisterWorkflow(freshCtx, workflow3) |
| 394 | + RegisterWorkflow(freshCtx, workflow4) |
| 395 | + |
| 396 | + // Test 3: Nested structs |
| 397 | + type InnerType struct { |
| 398 | + ID int |
| 399 | + } |
| 400 | + type OuterType struct { |
| 401 | + Inner InnerType |
| 402 | + Name string |
| 403 | + } |
| 404 | + |
| 405 | + workflow5 := func(ctx DBOSContext, input OuterType) (OuterType, error) { |
| 406 | + return input, nil |
| 407 | + } |
| 408 | + workflow6 := func(ctx DBOSContext, input *OuterType) (*OuterType, error) { |
| 409 | + return input, nil |
| 410 | + } |
| 411 | + |
| 412 | + RegisterWorkflow(freshCtx, workflow5) |
| 413 | + RegisterWorkflow(freshCtx, workflow6) |
| 414 | + |
| 415 | + // Test 4: Slice and map types |
| 416 | + workflow7 := func(ctx DBOSContext, input []TestType) ([]TestType, error) { |
| 417 | + return input, nil |
| 418 | + } |
| 419 | + workflow8 := func(ctx DBOSContext, input []*TestType) ([]*TestType, error) { |
| 420 | + return input, nil |
| 421 | + } |
| 422 | + workflow9 := func(ctx DBOSContext, input map[string]TestType) (map[string]TestType, error) { |
| 423 | + return input, nil |
| 424 | + } |
| 425 | + workflow10 := func(ctx DBOSContext, input map[string]*TestType) (map[string]*TestType, error) { |
| 426 | + return input, nil |
| 427 | + } |
| 428 | + |
| 429 | + RegisterWorkflow(freshCtx, workflow7) |
| 430 | + RegisterWorkflow(freshCtx, workflow8) |
| 431 | + RegisterWorkflow(freshCtx, workflow9) |
| 432 | + RegisterWorkflow(freshCtx, workflow10) |
| 433 | + |
| 434 | + // Launch and verify the system still works |
| 435 | + err := Launch(freshCtx) |
| 436 | + require.NoError(t, err, "failed to launch DBOS after gob conflict handling") |
| 437 | + defer Shutdown(freshCtx, 10*time.Second) |
| 438 | + |
| 439 | + // Test all registered workflows to ensure they work correctly |
| 440 | + |
| 441 | + // Run workflow1 with value type |
| 442 | + testValue := TestType{Value: "test"} |
| 443 | + handle1, err := RunWorkflow(freshCtx, workflow1, testValue) |
| 444 | + require.NoError(t, err, "failed to run workflow1") |
| 445 | + result1, err := handle1.GetResult() |
| 446 | + require.NoError(t, err, "failed to get result from workflow1") |
| 447 | + assert.Equal(t, testValue, result1, "unexpected result from workflow1") |
| 448 | + |
| 449 | + // Run workflow2 with pointer type |
| 450 | + testPointer := &TestType{Value: "pointer"} |
| 451 | + handle2, err := RunWorkflow(freshCtx, workflow2, testPointer) |
| 452 | + require.NoError(t, err, "failed to run workflow2") |
| 453 | + result2, err := handle2.GetResult() |
| 454 | + require.NoError(t, err, "failed to get result from workflow2") |
| 455 | + assert.Equal(t, testPointer, result2, "unexpected result from workflow2") |
| 456 | + |
| 457 | + // Run workflow3 with modified output |
| 458 | + handle3, err := RunWorkflow(freshCtx, workflow3, testValue) |
| 459 | + require.NoError(t, err, "failed to run workflow3") |
| 460 | + result3, err := handle3.GetResult() |
| 461 | + require.NoError(t, err, "failed to get result from workflow3") |
| 462 | + assert.Equal(t, TestType{Value: "test-modified"}, result3, "unexpected result from workflow3") |
| 463 | + |
| 464 | + // Run workflow5 with nested struct |
| 465 | + testOuter := OuterType{Inner: InnerType{ID: 42}, Name: "test"} |
| 466 | + handle5, err := RunWorkflow(freshCtx, workflow5, testOuter) |
| 467 | + require.NoError(t, err, "failed to run workflow5") |
| 468 | + result5, err := handle5.GetResult() |
| 469 | + require.NoError(t, err, "failed to get result from workflow5") |
| 470 | + assert.Equal(t, testOuter, result5, "unexpected result from workflow5") |
| 471 | + |
| 472 | + // Run workflow6 with nested struct pointer |
| 473 | + testOuterPtr := &OuterType{Inner: InnerType{ID: 43}, Name: "test-ptr"} |
| 474 | + handle6, err := RunWorkflow(freshCtx, workflow6, testOuterPtr) |
| 475 | + require.NoError(t, err, "failed to run workflow6") |
| 476 | + result6, err := handle6.GetResult() |
| 477 | + require.NoError(t, err, "failed to get result from workflow6") |
| 478 | + assert.Equal(t, testOuterPtr, result6, "unexpected result from workflow6") |
| 479 | + |
| 480 | + // Run workflow7 with slice type |
| 481 | + testSlice := []TestType{{Value: "a"}, {Value: "b"}} |
| 482 | + handle7, err := RunWorkflow(freshCtx, workflow7, testSlice) |
| 483 | + require.NoError(t, err, "failed to run workflow7") |
| 484 | + result7, err := handle7.GetResult() |
| 485 | + require.NoError(t, err, "failed to get result from workflow7") |
| 486 | + assert.Equal(t, testSlice, result7, "unexpected result from workflow7") |
| 487 | + |
| 488 | + // Run workflow8 with pointer slice type |
| 489 | + testPtrSlice := []*TestType{{Value: "a"}, {Value: "b"}} |
| 490 | + handle8, err := RunWorkflow(freshCtx, workflow8, testPtrSlice) |
| 491 | + require.NoError(t, err, "failed to run workflow8") |
| 492 | + result8, err := handle8.GetResult() |
| 493 | + require.NoError(t, err, "failed to get result from workflow8") |
| 494 | + assert.Equal(t, testPtrSlice, result8, "unexpected result from workflow8") |
| 495 | + |
| 496 | + // Run workflow9 with map type |
| 497 | + testMap := map[string]TestType{"key1": {Value: "value1"}} |
| 498 | + handle9, err := RunWorkflow(freshCtx, workflow9, testMap) |
| 499 | + require.NoError(t, err, "failed to run workflow9") |
| 500 | + result9, err := handle9.GetResult() |
| 501 | + require.NoError(t, err, "failed to get result from workflow9") |
| 502 | + assert.Equal(t, testMap, result9, "unexpected result from workflow9") |
| 503 | + |
| 504 | + // Run workflow10 with pointer map type |
| 505 | + testPtrMap := map[string]*TestType{"key1": {Value: "value1"}} |
| 506 | + handle10, err := RunWorkflow(freshCtx, workflow10, testPtrMap) |
| 507 | + require.NoError(t, err, "failed to run workflow10") |
| 508 | + result10, err := handle10.GetResult() |
| 509 | + require.NoError(t, err, "failed to get result from workflow10") |
| 510 | + assert.Equal(t, testPtrMap, result10, "unexpected result from workflow10") |
| 511 | + |
| 512 | + t.Run("validPanic", func(t *testing.T) { |
| 513 | + // Verify that non-duplicate registration panics are still propagated |
| 514 | + workflow11 := func(ctx DBOSContext, input any) (any, error) { |
| 515 | + return input, nil |
| 516 | + } |
| 517 | + |
| 518 | + // This should panic during registration because interface{} creates a nil value |
| 519 | + // which gob.Register cannot handle |
| 520 | + defer func() { |
| 521 | + r := recover() |
| 522 | + require.NotNil(t, r, "expected panic from interface{} registration but got none") |
| 523 | + // Verify it's not a duplicate registration error (which would be caught) |
| 524 | + if errStr, ok := r.(string); ok { |
| 525 | + assert.False(t, strings.Contains(errStr, "gob: registering duplicate"), |
| 526 | + "panic should not be a duplicate registration error, got: %v", r) |
| 527 | + } |
| 528 | + }() |
| 529 | + RegisterWorkflow(freshCtx, workflow11) // This should panic |
| 530 | + }) |
| 531 | + }) |
360 | 532 | } |
361 | 533 |
|
362 | 534 | func stepWithinAStep(ctx context.Context) (string, error) { |
|
0 commit comments