|
4 | 4 | "os" |
5 | 5 | "path/filepath" |
6 | 6 | "testing" |
| 7 | + "time" |
7 | 8 |
|
8 | 9 | "github.com/dagu-org/dagu/internal/core" |
9 | 10 | "github.com/dagu-org/dagu/internal/core/execution" |
@@ -94,3 +95,162 @@ func TestBaseDAGSpecialEnvVarsInHandler(t *testing.T) { |
94 | 95 | require.NotContains(t, outputStr, "DAG_RUN_ID=\n", "DAG_RUN_ID should not be empty") |
95 | 96 | require.NotContains(t, outputStr, "DAG_RUN_LOG_FILE=\n", "DAG_RUN_LOG_FILE should not be empty") |
96 | 97 | } |
| 98 | + |
| 99 | +func TestSkipBaseHandlers_SubDAGDoesNotInheritHandlers(t *testing.T) { |
| 100 | + t.Parallel() |
| 101 | + |
| 102 | + // Create a temp directory to store base config |
| 103 | + tmpDir := t.TempDir() |
| 104 | + baseConfigPath := filepath.Join(tmpDir, "base.yaml") |
| 105 | + markerFile := filepath.Join(tmpDir, "marker.txt") |
| 106 | + |
| 107 | + // Create base DAG with handlerOn: failure that writes a marker file |
| 108 | + baseConfig := `handlerOn: |
| 109 | + failure: |
| 110 | + command: echo "BASE_FAILURE_HANDLER_RAN" >> ` + markerFile + ` |
| 111 | +` |
| 112 | + require.NoError(t, os.WriteFile(baseConfigPath, []byte(baseConfig), 0600)) |
| 113 | + |
| 114 | + // Load a DAG WITHOUT skip base handlers - should have handler |
| 115 | + th := test.Setup(t) |
| 116 | + dagContent := `steps: |
| 117 | + - name: failing-step |
| 118 | + command: exit 1 |
| 119 | +` |
| 120 | + dagFile := th.CreateDAGFile(t, th.Config.Paths.DAGsDir, "test-no-skip", []byte(dagContent)) |
| 121 | + |
| 122 | + // Load without skip - should have failure handler from base config |
| 123 | + dagWithHandler, err := spec.Load(th.Context, dagFile, spec.WithBaseConfig(baseConfigPath)) |
| 124 | + require.NoError(t, err) |
| 125 | + require.NotNil(t, dagWithHandler.HandlerOn.Failure, "failure handler from base config should be set") |
| 126 | + |
| 127 | + // Load WITH skip base handlers - should NOT have handler |
| 128 | + dagWithoutHandler, err := spec.Load(th.Context, dagFile, spec.WithBaseConfig(baseConfigPath), spec.WithSkipBaseHandlers()) |
| 129 | + require.NoError(t, err) |
| 130 | + require.Nil(t, dagWithoutHandler.HandlerOn.Failure, "failure handler should NOT be inherited when skip flag is set") |
| 131 | +} |
| 132 | + |
| 133 | +func TestSkipBaseHandlers_ExplicitHandlersStillWork(t *testing.T) { |
| 134 | + t.Parallel() |
| 135 | + |
| 136 | + // Create a temp directory to store base config |
| 137 | + tmpDir := t.TempDir() |
| 138 | + baseConfigPath := filepath.Join(tmpDir, "base.yaml") |
| 139 | + baseMarkerFile := filepath.Join(tmpDir, "base_marker.txt") |
| 140 | + dagMarkerFile := filepath.Join(tmpDir, "dag_marker.txt") |
| 141 | + |
| 142 | + // Create base DAG with handlerOn: failure |
| 143 | + baseConfig := `handlerOn: |
| 144 | + failure: |
| 145 | + command: echo "BASE" >> ` + baseMarkerFile + ` |
| 146 | +` |
| 147 | + require.NoError(t, os.WriteFile(baseConfigPath, []byte(baseConfig), 0600)) |
| 148 | + |
| 149 | + // Setup test helper |
| 150 | + th := test.Setup(t) |
| 151 | + |
| 152 | + // Create a DAG file with its own failure handler |
| 153 | + dagContent := `handlerOn: |
| 154 | + failure: |
| 155 | + command: echo "DAG" >> ` + dagMarkerFile + ` |
| 156 | +
|
| 157 | +steps: |
| 158 | + - name: failing-step |
| 159 | + command: exit 1 |
| 160 | +` |
| 161 | + dagFile := th.CreateDAGFile(t, th.Config.Paths.DAGsDir, "test-explicit-handler", []byte(dagContent)) |
| 162 | + |
| 163 | + // Load WITH skip base handlers - should have DAG's own handler |
| 164 | + dag, err := spec.Load(th.Context, dagFile, spec.WithBaseConfig(baseConfigPath), spec.WithSkipBaseHandlers()) |
| 165 | + require.NoError(t, err) |
| 166 | + require.NotNil(t, dag.HandlerOn.Failure, "DAG's own failure handler should be present") |
| 167 | + |
| 168 | + // Run the DAG |
| 169 | + dagRunID := uuid.New().String() |
| 170 | + logDir := th.Config.Paths.LogDir |
| 171 | + logFile := filepath.Join(logDir, dagRunID+".log") |
| 172 | + root := execution.NewDAGRunRef(dag.Name, dagRunID) |
| 173 | + |
| 174 | + drm := runtimepkg.NewManager(th.DAGRunStore, th.ProcStore, th.Config) |
| 175 | + |
| 176 | + a := agent.New( |
| 177 | + dagRunID, |
| 178 | + dag, |
| 179 | + logDir, |
| 180 | + logFile, |
| 181 | + drm, |
| 182 | + th.DAGStore, |
| 183 | + th.DAGRunStore, |
| 184 | + th.ServiceRegistry, |
| 185 | + root, |
| 186 | + th.Config.Global.Peer, |
| 187 | + agent.Options{}, |
| 188 | + ) |
| 189 | + |
| 190 | + // Run the agent - expect failure |
| 191 | + err = a.Run(th.Context) |
| 192 | + require.Error(t, err) |
| 193 | + |
| 194 | + // Wait a bit for the handler file to be written |
| 195 | + time.Sleep(100 * time.Millisecond) |
| 196 | + |
| 197 | + // Verify DAG's own handler ran |
| 198 | + dagOutput, err := os.ReadFile(dagMarkerFile) |
| 199 | + require.NoError(t, err, "DAG's failure handler should have written marker file") |
| 200 | + require.Contains(t, string(dagOutput), "DAG", "DAG's own failure handler should have run") |
| 201 | + |
| 202 | + // Verify base handler did NOT run |
| 203 | + _, err = os.ReadFile(baseMarkerFile) |
| 204 | + require.True(t, os.IsNotExist(err), "base failure handler should NOT have run") |
| 205 | +} |
| 206 | + |
| 207 | +func TestSkipBaseHandlers_AllHandlerTypesSkipped(t *testing.T) { |
| 208 | + t.Parallel() |
| 209 | + |
| 210 | + // Create a temp directory to store base config |
| 211 | + tmpDir := t.TempDir() |
| 212 | + baseConfigPath := filepath.Join(tmpDir, "base.yaml") |
| 213 | + |
| 214 | + // Create base DAG with all handler types |
| 215 | + baseConfig := `handlerOn: |
| 216 | + init: |
| 217 | + command: "true" |
| 218 | + success: |
| 219 | + command: "true" |
| 220 | + failure: |
| 221 | + command: "true" |
| 222 | + abort: |
| 223 | + command: "true" |
| 224 | + exit: |
| 225 | + command: "true" |
| 226 | +` |
| 227 | + require.NoError(t, os.WriteFile(baseConfigPath, []byte(baseConfig), 0600)) |
| 228 | + |
| 229 | + // Setup test helper |
| 230 | + th := test.Setup(t) |
| 231 | + |
| 232 | + // Create a DAG file |
| 233 | + dagContent := `steps: |
| 234 | + - name: step1 |
| 235 | + command: "true" |
| 236 | +` |
| 237 | + dagFile := th.CreateDAGFile(t, th.Config.Paths.DAGsDir, "test-all-handlers", []byte(dagContent)) |
| 238 | + |
| 239 | + // Load without skip - all handlers should be set |
| 240 | + dagWithHandlers, err := spec.Load(th.Context, dagFile, spec.WithBaseConfig(baseConfigPath)) |
| 241 | + require.NoError(t, err) |
| 242 | + require.NotNil(t, dagWithHandlers.HandlerOn.Init, "init handler should be set") |
| 243 | + require.NotNil(t, dagWithHandlers.HandlerOn.Success, "success handler should be set") |
| 244 | + require.NotNil(t, dagWithHandlers.HandlerOn.Failure, "failure handler should be set") |
| 245 | + require.NotNil(t, dagWithHandlers.HandlerOn.Cancel, "abort/cancel handler should be set") |
| 246 | + require.NotNil(t, dagWithHandlers.HandlerOn.Exit, "exit handler should be set") |
| 247 | + |
| 248 | + // Load WITH skip - no handlers should be set |
| 249 | + dagWithoutHandlers, err := spec.Load(th.Context, dagFile, spec.WithBaseConfig(baseConfigPath), spec.WithSkipBaseHandlers()) |
| 250 | + require.NoError(t, err) |
| 251 | + require.Nil(t, dagWithoutHandlers.HandlerOn.Init, "init handler should NOT be set") |
| 252 | + require.Nil(t, dagWithoutHandlers.HandlerOn.Success, "success handler should NOT be set") |
| 253 | + require.Nil(t, dagWithoutHandlers.HandlerOn.Failure, "failure handler should NOT be set") |
| 254 | + require.Nil(t, dagWithoutHandlers.HandlerOn.Cancel, "abort/cancel handler should NOT be set") |
| 255 | + require.Nil(t, dagWithoutHandlers.HandlerOn.Exit, "exit handler should NOT be set") |
| 256 | +} |
0 commit comments