Skip to content

feat(spec): add init handler field#1455

Merged
yohamta0 merged 8 commits into
mainfrom
handler-on-init
Dec 6, 2025
Merged

feat(spec): add init handler field#1455
yohamta0 merged 8 commits into
mainfrom
handler-on-init

Conversation

@yohamta0

@yohamta0 yohamta0 commented Dec 6, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added an Init handler that runs (after preconditions) before workflow steps; its status is surfaced and failure prevents steps from running.
    • Added a canonical Abort handler and retained Cancel as deprecated.
    • Runtime and status now include OnInit nodes; option to skip inheriting base handlers for sub-DAG runs.
  • Documentation

    • Schema updated to document init and abort hooks and lifecycle ordering.
  • Tests

    • New integration tests for Init, abort/cancel behavior, skipping base handlers, and lifecycle paths.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 6, 2025

Copy link
Copy Markdown

Walkthrough

Adds an Init lifecycle handler that runs after preconditions and before workflow steps; Init failure prevents steps from running. Introduces a canonical Abort handler (deprecating Cancel), adds skip-base-handlers support for sub-DAGs, and wires Init/Abort through DAG model, spec, loader, builder, runtime (agent/runner/transform), schema, CLI, and integration tests.

Changes

Cohort / File(s) Summary
Core DAG model
internal/core/dag.go
Added Init *Step to HandlerOn and new HandlerOnInit = "onInit" constant.
DAG run status
internal/core/execution/runstatus.go
Added OnInit *Node to DAGRunStatus; errors reporting and NodeByName lookup include OnInit.
Spec definitions
internal/core/spec/definition.go
handlerOnDef gains Init *stepDef and Abort *stepDef; Cancel retained with deprecation note.
Spec builder
internal/core/spec/builder.go
Builder now builds Init; enforces mutual exclusivity between Abort and Cancel (error if both); maps Cancel to abort/cancel path when needed; added BuildFlagSkipBaseHandlers.
Spec loader / options
internal/core/spec/loader.go
Added WithSkipBaseHandlers() LoadOption; base-DAG build resets HandlerOn when skip flag set to avoid inheritance for sub-DAGs.
CLI call sites
internal/cmd/start.go, internal/cmd/enqueue.go
loadDAGWithParams signature gains isSubDAGRun bool; call sites updated to pass sub-DAG flag; sub-DAG runs apply skip-base-handlers option.
Runtime — Runner & Config
internal/runtime/runner.go
Added Runner.onInit *core.Step and Config.OnInit *core.Step; Runner evaluates preconditions, runs OnInit (if defined) before main steps, and cancels run on Init failure; OnInit registered with handlers.
Runtime — Agent & status transform
internal/runtime/agent/agent.go, internal/runtime/transform/status.go
Agent wires cfg.OnInit = dag.HandlerOn.Init; added WithOnInitNode(node *runtime.Node) StatusOption to include OnInit in DAGRun status.
Schemas
schemas/dag.schema.json
Added handlerOn.init; replaced handlerOn.cancel with handlerOn.abort (cancel deprecated); updated handlerOn description.
Integration tests
internal/integration/handler_on_test.go, internal/integration/base_dag_test.go
New tests covering Init success/failure, precondition interactions, Abort/Cancel mapping, async abort, and skip-base-handlers behavior for sub-DAGs.

Sequence Diagram

sequenceDiagram
    participant User as User/Executor
    participant Agent as Agent
    participant Runner as Runner
    participant Handlers as Handlers
    participant Steps as Workflow Steps

    User->>Agent: Start DAG run
    Agent->>Agent: Parse DAG spec (Init, Abort, etc.)
    Agent->>Runner: newRunner(cfg with OnInit)
    Runner->>Runner: Evaluate preconditions
    alt Preconditions pass
        Runner->>Handlers: Execute OnInit (if defined)
        alt OnInit succeeds
            Runner->>Steps: Execute workflow steps
            alt Steps succeed
                Runner->>Handlers: Execute OnSuccess
                Runner->>Handlers: Execute OnExit
                Runner->>Agent: Report Succeeded
            else Steps fail
                Runner->>Handlers: Execute OnFailure
                Runner->>Handlers: Execute OnExit
                Runner->>Agent: Report Failed
            end
        else OnInit fails
            Runner->>Handlers: Execute OnExit
            Runner->>Agent: Report Aborted (Init failure)
        end
    else Preconditions fail
        Runner->>Agent: Report Aborted (preconditions)
    end

    alt Manual abort during run
        User->>Runner: Abort()
        Runner->>Handlers: Execute OnAbort/OnCancel (mapped to Abort)
        Runner->>Handlers: Execute OnExit
        Runner->>Agent: Report Aborted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • internal/runtime/runner.go — placement of Init execution, precondition interplay, status transitions when Init fails.
    • internal/core/spec/builder.go & internal/core/spec/loader.go — Abort vs Cancel handling, BuildFlagSkipBaseHandlers semantics and propagation to sub-DAG loads.
    • internal/core/execution/runstatus.go and internal/runtime/transform/status.go — consistent OnInit wiring, serialization, and NodeByName behavior.
    • internal/integration/* tests — ensure expectations reflect lifecycle behavior (Init skip, abort mapping, inheritance skipping).

Poem

🐇 I nudged the DAG to wake at dawn,
A tiny Init hop before steps spawn.
If I tumble, the run will pause—
Abort now stands where Cancel once was.
Hop, test, repeat—then carry on.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary feature being added—init handler support. While the changeset encompasses additional features (abort handler, skip base handlers), the init handler is the foundational enhancement highlighted in the title.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch handler-on-init

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58e29a6 and 0cba17c.

📒 Files selected for processing (1)
  • internal/integration/handler_on_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/integration/handler_on_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/handler_on_test.go
🧠 Learnings (1)
📚 Learning: 2025-12-04T10:34:17.051Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.051Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/handler_on_test.go
🧬 Code graph analysis (1)
internal/integration/handler_on_test.go (3)
internal/core/dag.go (2)
  • DAG (33-135)
  • HandlerOn (476-482)
internal/core/execution/runstatus.go (1)
  • DAGRunStatus (35-57)
internal/core/status.go (9)
  • Succeeded (11-11)
  • NodeSucceeded (57-57)
  • Aborted (10-10)
  • NodeFailed (55-55)
  • NodeNotStarted (53-53)
  • NodeSkipped (58-58)
  • NodeAborted (56-56)
  • Failed (9-9)
  • Running (8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (2)
internal/integration/handler_on_test.go (2)

216-257: Well-implemented asynchronous abort test.

The abort handler test correctly:

  • Uses goroutine and channel synchronization to handle async DAG execution
  • Waits for Running state before triggering abort (line 244)
  • Verifies the mapping from YAML abort field to internal Cancel field (lines 232-233)
  • Validates abort handler execution and final Aborted status

The async pattern is appropriate for testing abort/cancel semantics.


23-195: Comprehensive test coverage of lifecycle handlers.

The test cases effectively validate:

  • Init handler success and failure scenarios
  • Init handler precondition skip (handler-level)
  • DAG-level precondition preventing Init execution
  • Failure, Success, and Exit handlers

This thorough coverage aligns well with the PR objectives to introduce the Init handler field and validates proper integration with existing handlers.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
schemas/dag.schema.json (1)

172-198: Schema additions for handlerOn.init/abort/cancel align with runtime behavior

The new init and abort properties and the updated cancel description accurately describe how the runtime treats these hooks (init before steps; abort/cancel on aborted DAG; cancel deprecated). If you expect users to rely heavily on init preconditions, consider later clarifying in the description whether “If this fails” refers specifically to the init command failing vs. its preconditions being unmet, but this is not a blocker.

internal/core/spec/builder.go (1)

724-778: HandlerOn Init and Abort/Cancel build logic is sound; consider warning on deprecated cancel usage

The Init handler is built consistently with the other hooks, and the Abort/Cancel logic (prefer abort, error if both are set, map the chosen one into dag.HandlerOn.Cancel with the onCancel name) cleanly implements the “Abort canonical / Cancel deprecated alias” story.

As a small UX improvement, you might want to surface a deprecation warning when handlerOn.cancel is used without handlerOn.abort, similar to how run vs call is handled elsewhere, e.g.:

@@ func buildHandlers(ctx BuildContext, spec *definition, dag *core.DAG) (err error) {
-   var abortDef *stepDef
+   var abortDef *stepDef
    switch {
    case spec.HandlerOn.Abort != nil:
        abortDef = spec.HandlerOn.Abort
    case spec.HandlerOn.Cancel != nil:
+       // handlerOn.cancel is deprecated; prefer handlerOn.abort
+       message := "handlerOn.cancel is deprecated, use handlerOn.abort instead"
+       logger.Warn(ctx.ctx, message)
+       dag.BuildWarnings = append(dag.BuildWarnings, message)
        abortDef = spec.HandlerOn.Cancel
    }

Not required for correctness but would help users migrate off cancel.

internal/integration/handler_on_test.go (2)

127-164: Add timeout protection to prevent hanging test.

The concurrent abort test lacks timeout protection. If the DAG fails to complete, the test will hang indefinitely at line 157 (<-done).

Apply this diff to add timeout protection:

+	// Wait for the DAG to start running
 	dag.AssertLatestStatus(t, core.Running)
 
 	// Abort the DAG
 	dagAgent.Abort()
 
 	// Wait for completion
-	<-done
+	select {
+	case <-done:
+		// Success
+	case <-time.After(5 * time.Second):
+		t.Fatal("timeout waiting for DAG to complete after abort")
+	}

Don't forget to add "time" to the imports.


11-225: Consider table-driven test structure to reduce duplication.

While the current subtest structure is clear, the coding guidelines recommend table-driven cases. A table-driven approach would reduce duplication in DAG YAML setup and status assertion patterns, making it easier to add new test cases.

Based on learnings, co-located Go tests should favour table-driven cases.

Example refactor:

func TestHandlerOn(t *testing.T) {
	t.Parallel()
	
	tests := []struct {
		name          string
		dagYAML       string
		runFunc       func(*testing.T, *test.Agent)
		validateFunc  func(*testing.T, *core.DAGRunStatus)
	}{
		{
			name: "InitHandler_Success",
			dagYAML: `
handlerOn:
  init:
    command: "true"
steps:
  - name: step1
    command: "true"
`,
			runFunc: func(t *testing.T, agent *test.Agent) {
				agent.RunSuccess(t)
			},
			validateFunc: func(t *testing.T, status *core.DAGRunStatus) {
				require.Equal(t, core.Succeeded, status.Status)
				require.NotNil(t, status.OnInit)
				require.Equal(t, core.NodeSucceeded, status.OnInit.Status)
			},
		},
		// ... more test cases
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			th := test.Setup(t)
			dag := th.DAG(t, tt.dagYAML)
			agent := dag.Agent()
			tt.runFunc(t, agent)
			status := agent.Status(th.Context)
			tt.validateFunc(t, status)
		})
	}
}

Note: The AbortHandler test would need special handling due to its concurrency pattern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07929d9 and 435027d.

📒 Files selected for processing (9)
  • internal/core/dag.go (2 hunks)
  • internal/core/execution/runstatus.go (4 hunks)
  • internal/core/spec/builder.go (2 hunks)
  • internal/core/spec/definition.go (1 hunks)
  • internal/integration/handler_on_test.go (1 hunks)
  • internal/runtime/agent/agent.go (2 hunks)
  • internal/runtime/runner.go (6 hunks)
  • internal/runtime/transform/status.go (1 hunks)
  • schemas/dag.schema.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/runtime/transform/status.go
  • internal/runtime/runner.go
  • internal/core/spec/definition.go
  • internal/core/execution/runstatus.go
  • internal/core/dag.go
  • internal/integration/handler_on_test.go
  • internal/runtime/agent/agent.go
  • internal/core/spec/builder.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/handler_on_test.go
🧠 Learnings (1)
📚 Learning: 2025-12-04T10:34:17.051Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.051Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/handler_on_test.go
🧬 Code graph analysis (7)
internal/runtime/transform/status.go (2)
internal/core/execution/runstatus.go (1)
  • DAGRunStatus (35-57)
internal/runtime/data.go (1)
  • NodeData (23-26)
internal/runtime/runner.go (7)
internal/core/step.go (1)
  • Step (13-77)
internal/common/logger/tag/tag.go (3)
  • Step (25-27)
  • Handler (332-334)
  • Name (271-273)
internal/core/dag.go (1)
  • HandlerOnInit (520-520)
internal/common/logger/context.go (1)
  • Debug (35-37)
internal/core/status.go (1)
  • NodeSkipped (58-58)
internal/runtime/node.go (1)
  • Node (33-43)
internal/runtime/data.go (2)
  • Data (17-20)
  • NodeData (23-26)
internal/core/execution/runstatus.go (2)
internal/core/execution/node.go (2)
  • NewNodeOrNil (52-57)
  • Node (9-24)
internal/core/dag.go (1)
  • HandlerOn (476-482)
internal/core/dag.go (2)
api/v2/api.gen.go (1)
  • Step (781-844)
internal/core/step.go (1)
  • Step (13-77)
internal/integration/handler_on_test.go (2)
internal/core/dag.go (1)
  • HandlerOn (476-482)
internal/core/status.go (9)
  • Succeeded (11-11)
  • NodeSucceeded (57-57)
  • Aborted (10-10)
  • NodeFailed (55-55)
  • NodeNotStarted (53-53)
  • NodeSkipped (58-58)
  • NodeAborted (56-56)
  • Running (8-8)
  • Failed (9-9)
internal/runtime/agent/agent.go (4)
internal/runtime/transform/status.go (1)
  • WithOnInitNode (74-80)
internal/core/dag.go (2)
  • HandlerOnInit (520-520)
  • HandlerOn (476-482)
api/v2/api.gen.go (1)
  • HandlerOn (447-459)
api/v1/api.gen.go (1)
  • HandlerOn (358-370)
internal/core/spec/builder.go (2)
internal/core/dag.go (3)
  • HandlerOn (476-482)
  • HandlerOnInit (520-520)
  • HandlerOnCancel (523-523)
api/v2/api.gen.go (1)
  • HandlerOn (447-459)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (12)
internal/core/dag.go (1)

475-482: Init handler field and handler type constant are consistent with existing lifecycle hooks

HandlerOn.Init with json:"init,omitempty" plus the HandlerOnInit constant mirrors the existing success/failure/cancel/exit pattern and lines up with the builder, runtime, and schema usage. No issues from a core-model perspective.

Also applies to: 519-525

internal/core/spec/definition.go (1)

93-101: HandlerOn Init/Abort/Cancel definition cleanly matches intended YAML shape

The added Init and Abort fields plus the deprecation comment on Cancel accurately model the new lifecycle hooks and backward compatibility without impacting other spec fields.

internal/runtime/transform/status.go (1)

73-80: WithOnInitNode correctly mirrors existing status options

The new option cleanly follows the existing pattern (OnExit/OnSuccess/OnFailure/OnCancel), safely handles nil, and ensures OnInit in DAGRunStatus is populated from the runtime node when present.

internal/core/execution/runstatus.go (1)

15-31: OnInit support in run status is wired consistently

Initializing OnInit in InitialStatus, adding the OnInit field to DAGRunStatus, and extending both Errors() and NodeByName() to include it keeps init handlers first‑class alongside exit/success/failure/cancel. The behavior is internally consistent and should integrate cleanly with the new status transformer option.

Also applies to: 35-57, 65-88, 91-113

internal/runtime/agent/agent.go (1)

610-633: Agent wiring for Init handler into runner config and status looks correct

Passing a.dag.HandlerOn.Init into cfg.OnInit and adding transform.WithOnInitNode(a.runner.HandlerNode(core.HandlerOnInit)) alongside the existing handler options keeps Agent → Runner → Status behavior consistent with other lifecycle hooks. The ordering in Status (nodes + log + init/exit/success/failure/cancel) is sensible.

Also applies to: 744-780

internal/runtime/runner.go (1)

35-45: Init handler orchestration and handler precondition evaluation look correct; double‑check intended semantics

The runtime changes hang together well:

  • Runner/Config now carry onInit/OnInit, and setup() registers an HandlerOnInit node just like the other handlers.
  • In Run, the init handler executes only after DAG-level preconditions pass and before any workflow nodes. If runEventHandler returns a non‑nil error (i.e., the init step’s Execute fails), setLastError and setCanceled are called, so the event loop exits with core.Aborted and no steps are run, matching the schema’s “if this fails, the DAG fails and no steps execute” intent.
  • WithOnInitNode(r.HandlerNode(core.HandlerOnInit)) ensures status objects carry the init handler node state, in line with OnExit/OnSuccess/OnFailure/OnCancel.

Two subtle points you may want to confirm against your expectations/tests:

  • In runEventHandler, failures in Prepare or in evaluating handler preconditions now set the handler status (failed or skipped) but return nil, so for the init handler only an actual Execute error will cancel the DAG. If you ever need preparatory/logging failures on init to abort the DAG too, you might want to propagate those as errors specifically for HandlerOnInit.
  • setupEnvironEventHandler seeds Env[EnvKeyDAGRunStatus] from r.Status(ctx, plan). For onInit this will typically be NotStarted at the time the handler runs, whereas for exit/success/failure/cancel it reflects the final DAG status. If you expect init scripts to branch on DAG status, it’s worth confirming that this asymmetry is acceptable.

Overall, the init lifecycle hook is integrated consistently; just ensure these edge semantics line up with what you want long‑term (your new integration tests likely already cover most of this).

Also applies to: 66-76, 83-95, 128-143, 680-705, 724-743

internal/integration/handler_on_test.go (6)

1-9: LGTM: Clean test setup.

Package naming and imports follow Go conventions and coding guidelines, properly using stretchr/testify/require as specified.


11-12: LGTM: Proper parallel test setup.

Using t.Parallel() for the integration test suite is appropriate and improves test execution time.


14-36: LGTM: Comprehensive happy path test.

The test properly verifies both parsing (Init field and name) and runtime execution (OnInit status), with clear assertions.


38-67: LGTM: Thorough failure path test.

Excellent coverage of the init failure scenario, verifying the abort status, exit handler execution, and that subsequent steps don't run. The failure path testing aligns with coding guidelines.


69-125: LGTM: Good edge case coverage for preconditions.

Both tests properly handle precondition scenarios with appropriate assertions. The conditional checks (lines 114-116, 121-124) correctly handle timing-dependent behavior and are well-documented.


166-224: LGTM: Clear and consistent handler tests.

The Failure, Success, and Exit handler tests follow a consistent pattern and properly verify each handler's execution and status.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c178b89 and 58e29a6.

📒 Files selected for processing (1)
  • internal/integration/handler_on_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Backend entrypoint in cmd/ orchestrates the scheduler and CLI; runtime, persistence, and service layers sit under internal/* (for example internal/runtime, internal/persistence)
Keep Go files gofmt/goimports clean; use tabs, PascalCase for exported symbols (SchedulerClient), lowerCamelCase for locals, and Err... names for package-level errors
Repository linting relies on golangci-lint; prefer idiomatic Go patterns, minimal global state, and structured logging helpers in internal/common

Files:

  • internal/integration/handler_on_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Co-locate Go tests as *_test.go; favour table-driven cases and cover failure paths
Use stretchr/testify/require and shared fixtures from internal/test instead of duplicating mocks

Files:

  • internal/integration/handler_on_test.go
🧠 Learnings (1)
📚 Learning: 2025-12-04T10:34:17.051Z
Learnt from: CR
Repo: dagu-org/dagu PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T10:34:17.051Z
Learning: Applies to **/*_test.go : Co-locate Go tests as `*_test.go`; favour table-driven cases and cover failure paths

Applied to files:

  • internal/integration/handler_on_test.go
🪛 GitHub Check: Go Linter
internal/integration/handler_on_test.go

[failure] 147-147:
unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 115-115:
unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 90-90:
unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 60-60:
unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 38-38:
unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _ (revive)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test on ubuntu-latest
🔇 Additional comments (5)
internal/integration/handler_on_test.go (5)

1-11: LGTM!

The package declaration and imports are clean and follow Go best practices. The use of an external test package (integration_test) is appropriate for integration tests.


13-196: Excellent test coverage for HandlerOn feature.

The table-driven test structure is well-organized and covers the key scenarios: Init (success, failure, precondition skip, DAG precondition), Failure, Success, and Exit handlers. The use of optional setup/run/validate functions provides good flexibility.


48-77: Well-structured test for Init failure behavior.

The test correctly verifies that Init handler failure causes DAG abortion, triggers the exit handler, and prevents step execution. The validation logic properly checks all expected states.


103-135: Good defensive handling of timing-dependent states.

The test properly handles cases where OnInit may be nil or NotStarted when DAG preconditions fail, and accounts for timing-dependent node status (NotStarted or Aborted). This makes the test more robust.


216-257: Well-designed async abort test.

The test properly handles the asynchronous nature of abort handling using a goroutine and synchronization channel. The pattern of waiting for the DAG to reach Running status before aborting ensures deterministic test behavior. The comment on line 231 helpfully clarifies the mapping between the YAML abort field and internal Cancel handler.

Comment thread internal/integration/handler_on_test.go Outdated
@ghansham

ghansham commented Dec 6, 2025

Copy link
Copy Markdown

Can we create variables derived from parameters in the initHandler? Some example will be appreciable.

@yohamta0

yohamta0 commented Dec 6, 2025

Copy link
Copy Markdown
Collaborator Author

@ghansham Yes, you can use output field to define variable same as regular steps.

@yohamta0 yohamta0 merged commit 78844bf into main Dec 6, 2025
5 checks passed
@yohamta0 yohamta0 deleted the handler-on-init branch December 6, 2025 19:31
@codecov

codecov Bot commented Dec 8, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 16.66667% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.76%. Comparing base (07929d9) to head (0cba17c).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
internal/runtime/runner.go 17.64% 11 Missing and 3 partials ⚠️
internal/core/spec/builder.go 13.33% 10 Missing and 3 partials ⚠️
internal/core/execution/runstatus.go 0.00% 5 Missing ⚠️
internal/core/spec/loader.go 0.00% 4 Missing and 1 partial ⚠️
internal/runtime/transform/status.go 0.00% 4 Missing ⚠️
internal/cmd/start.go 50.00% 1 Missing and 1 partial ⚠️
internal/runtime/agent/agent.go 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1455      +/-   ##
==========================================
- Coverage   59.86%   59.76%   -0.10%     
==========================================
  Files         188      188              
  Lines       21167    21214      +47     
==========================================
+ Hits        12671    12678       +7     
- Misses       7180     7213      +33     
- Partials     1316     1323       +7     
Files with missing lines Coverage Δ
internal/cmd/enqueue.go 62.16% <100.00%> (ø)
internal/core/dag.go 44.09% <ø> (ø)
internal/cmd/start.go 46.69% <50.00%> (-0.39%) ⬇️
internal/runtime/agent/agent.go 49.90% <33.33%> (-0.10%) ⬇️
internal/runtime/transform/status.go 93.54% <0.00%> (-6.46%) ⬇️
internal/core/execution/runstatus.go 4.68% <0.00%> (-0.40%) ⬇️
internal/core/spec/loader.go 63.88% <0.00%> (-1.30%) ⬇️
internal/core/spec/builder.go 67.20% <13.33%> (-0.55%) ⬇️
internal/runtime/runner.go 82.38% <17.64%> (-1.46%) ⬇️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 07929d9...0cba17c. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants