@ai-setting/roy-agent-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +99145 -0
- package/package.json +114 -0
- package/src/config/config-component.test.ts +627 -0
- package/src/config/config-component.ts +906 -0
- package/src/config/config-parser.test.ts +319 -0
- package/src/config/config-parser.ts +203 -0
- package/src/config/decentralized-config.test.ts +740 -0
- package/src/config/env-key.ts +210 -0
- package/src/config/env-source.test.ts +252 -0
- package/src/config/env-source.ts +301 -0
- package/src/config/file-source.test.ts +357 -0
- package/src/config/file-source.ts +421 -0
- package/src/config/index.ts +24 -0
- package/src/config/protocol-resolver.test.ts +217 -0
- package/src/config/protocol-resolver.ts +228 -0
- package/src/env/agent/agent-component.abort.test.ts +511 -0
- package/src/env/agent/agent-component.record-session.test.ts +349 -0
- package/src/env/agent/agent-component.test.ts +1389 -0
- package/src/env/agent/agent-component.tool-error.test.ts +327 -0
- package/src/env/agent/agent-component.ts +1711 -0
- package/src/env/agent/agent-config-registration.test.ts +226 -0
- package/src/env/agent/agent-config-registration.ts +46 -0
- package/src/env/agent/agent-reminder-plugin.integration.test.ts +243 -0
- package/src/env/agent/index.ts +10 -0
- package/src/env/agent/summary-agent.parse-hint.test.ts +360 -0
- package/src/env/agent/summary-agent.ts +508 -0
- package/src/env/agent/types.ts +536 -0
- package/src/env/commands/commands-component.test.ts +364 -0
- package/src/env/commands/commands-component.ts +604 -0
- package/src/env/commands/commands-config-registration.test.ts +198 -0
- package/src/env/commands/commands-config-registration.ts +38 -0
- package/src/env/commands/index.ts +21 -0
- package/src/env/commands/parser.test.ts +203 -0
- package/src/env/commands/parser.ts +115 -0
- package/src/env/commands/types.ts +184 -0
- package/src/env/commands-prompt-integration.test.ts +243 -0
- package/src/env/component-env.test.ts +119 -0
- package/src/env/component.ts +335 -0
- package/src/env/constants.test.ts +72 -0
- package/src/env/constants.ts +123 -0
- package/src/env/debug/debug-component.test.ts +114 -0
- package/src/env/debug/debug-component.ts +547 -0
- package/src/env/debug/formatters/index.ts +9 -0
- package/src/env/debug/formatters/repl-formatter.test.ts +139 -0
- package/src/env/debug/formatters/repl-formatter.ts +358 -0
- package/src/env/debug/formatters/trace-formatter.test.ts +119 -0
- package/src/env/debug/formatters/trace-formatter.ts +191 -0
- package/src/env/debug/formatters/tree-formatter.test.ts +107 -0
- package/src/env/debug/formatters/tree-formatter.ts +325 -0
- package/src/env/debug/index.ts +38 -0
- package/src/env/debug/parser/regex-parser.test.ts +201 -0
- package/src/env/debug/parser/regex-parser.ts +196 -0
- package/src/env/debug/parser/span-builder.test.ts +241 -0
- package/src/env/debug/parser/span-builder.ts +386 -0
- package/src/env/debug/reader/log-reader.test.ts +170 -0
- package/src/env/debug/reader/log-reader.ts +186 -0
- package/src/env/debug/reader/span-db-reader.test.ts +118 -0
- package/src/env/debug/reader/span-db-reader.ts +201 -0
- package/src/env/debug/types.test.ts +187 -0
- package/src/env/debug/types.ts +171 -0
- package/src/env/environment-init.test.ts +183 -0
- package/src/env/environment-lifecycle.test.ts +516 -0
- package/src/env/environment-service.test.ts +332 -0
- package/src/env/environment.handle-query.test.ts +96 -0
- package/src/env/environment.test.ts +232 -0
- package/src/env/environment.ts +708 -0
- package/src/env/errors.test.ts +165 -0
- package/src/env/errors.ts +157 -0
- package/src/env/event-source/event-source-agent-handler.test.ts +193 -0
- package/src/env/event-source/event-source-agent-handler.ts +111 -0
- package/src/env/event-source/event-source-component.process-cleanup.test.ts +236 -0
- package/src/env/event-source/event-source-component.stop.test.ts +346 -0
- package/src/env/event-source/event-source-component.test.ts +1207 -0
- package/src/env/event-source/event-source-component.ts +1379 -0
- package/src/env/event-source/event-source-config-registration.test.ts +242 -0
- package/src/env/event-source/event-source-config-registration.ts +37 -0
- package/src/env/event-source/event-source-integration.test.ts +320 -0
- package/src/env/event-source/event-source-platform.test.ts +630 -0
- package/src/env/event-source/types.ts +298 -0
- package/src/env/hook/global-hook-manager.ts +162 -0
- package/src/env/hook/hook-manager.test.ts +374 -0
- package/src/env/hook/hook-manager.ts +309 -0
- package/src/env/hook/index.ts +38 -0
- package/src/env/hook/types.ts +138 -0
- package/src/env/index.ts +144 -0
- package/src/env/interface.ts +203 -0
- package/src/env/llm/hooks.test.ts +293 -0
- package/src/env/llm/hooks.ts +316 -0
- package/src/env/llm/index.ts +61 -0
- package/src/env/llm/invoke-threshold-check.test.ts +88 -0
- package/src/env/llm/invoke-timeout.test.ts +54 -0
- package/src/env/llm/invoke.test.ts +71 -0
- package/src/env/llm/invoke.ts +1039 -0
- package/src/env/llm/llm-config.test.ts +523 -0
- package/src/env/llm/llm.test.ts +233 -0
- package/src/env/llm/llm.ts +568 -0
- package/src/env/llm/provider.test.ts +182 -0
- package/src/env/llm/provider.ts +108 -0
- package/src/env/llm/transform.test.ts +251 -0
- package/src/env/llm/transform.ts +286 -0
- package/src/env/llm/types.test.ts +580 -0
- package/src/env/llm/types.ts +424 -0
- package/src/env/log-trace/decorator-otel.test.ts +182 -0
- package/src/env/log-trace/decorator.ts +230 -0
- package/src/env/log-trace/index.ts +79 -0
- package/src/env/log-trace/log-trace-component.test.ts +242 -0
- package/src/env/log-trace/log-trace-component.ts +497 -0
- package/src/env/log-trace/log-trace-config-registration.test.ts +348 -0
- package/src/env/log-trace/log-trace-config-registration.ts +45 -0
- package/src/env/log-trace/logger.test.ts +149 -0
- package/src/env/log-trace/logger.ts +522 -0
- package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +147 -0
- package/src/env/log-trace/opentelemetry/cli-propagation.ts +194 -0
- package/src/env/log-trace/opentelemetry/integration.test.ts +668 -0
- package/src/env/log-trace/opentelemetry/mod.ts +25 -0
- package/src/env/log-trace/opentelemetry/propagation-env.test.ts +181 -0
- package/src/env/log-trace/opentelemetry/propagation-env.ts +136 -0
- package/src/env/log-trace/opentelemetry/propagation.test.ts +259 -0
- package/src/env/log-trace/opentelemetry/propagation.ts +215 -0
- package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +166 -0
- package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +379 -0
- package/src/env/log-trace/opentelemetry/tracer-provider.ts +612 -0
- package/src/env/log-trace/span-storage.test.ts +145 -0
- package/src/env/log-trace/span-storage.ts +230 -0
- package/src/env/log-trace/trace-context.test.ts +187 -0
- package/src/env/log-trace/trace-context.ts +162 -0
- package/src/env/log-trace/types.test.ts +63 -0
- package/src/env/log-trace/types.ts +172 -0
- package/src/env/mcp/README.md +244 -0
- package/src/env/mcp/__integration__/mcp-component.integration.test.ts +373 -0
- package/src/env/mcp/config.test.ts +74 -0
- package/src/env/mcp/config.ts +116 -0
- package/src/env/mcp/index.ts +41 -0
- package/src/env/mcp/loader.test.ts +161 -0
- package/src/env/mcp/loader.ts +209 -0
- package/src/env/mcp/mcp-component.test.ts +111 -0
- package/src/env/mcp/mcp-component.ts +358 -0
- package/src/env/mcp/mcp-config-registration.test.ts +304 -0
- package/src/env/mcp/mcp-config-registration.ts +50 -0
- package/src/env/mcp/scanner.test.ts +170 -0
- package/src/env/mcp/scanner.ts +246 -0
- package/src/env/mcp/tool/adapter.test.ts +520 -0
- package/src/env/mcp/tool/adapter.ts +521 -0
- package/src/env/mcp/tool/index.ts +5 -0
- package/src/env/mcp/types.test.ts +171 -0
- package/src/env/mcp/types.ts +79 -0
- package/src/env/memory/README.md +177 -0
- package/src/env/memory/built-in/index.ts +59 -0
- package/src/env/memory/built-in/recall-memory.ts +103 -0
- package/src/env/memory/built-in/record-memory.ts +148 -0
- package/src/env/memory/index.ts +20 -0
- package/src/env/memory/memory-component.test.ts +239 -0
- package/src/env/memory/memory-component.ts +503 -0
- package/src/env/memory/memory-config-registration.test.ts +67 -0
- package/src/env/memory/memory-config-registration.ts +48 -0
- package/src/env/memory/memory-config.ts +45 -0
- package/src/env/memory/memory-file.test.ts +268 -0
- package/src/env/memory/plugin/index.ts +48 -0
- package/src/env/memory/plugin/memory-agent.test.ts +249 -0
- package/src/env/memory/plugin/memory-agent.ts +365 -0
- package/src/env/memory/plugin/memory-manager.ts +198 -0
- package/src/env/memory/plugin/memory-plugin-agent.test.ts +145 -0
- package/src/env/memory/plugin/memory-plugin.ts +210 -0
- package/src/env/memory/plugin/plugin-simplified.test.ts +51 -0
- package/src/env/memory/plugin/recall-memory.test.ts +106 -0
- package/src/env/memory/plugin/recall-memory.ts +53 -0
- package/src/env/memory/plugin/types.ts +101 -0
- package/src/env/memory/tools/memory-agent-tools.ts +228 -0
- package/src/env/memory/types.ts +85 -0
- package/src/env/paths.ts +118 -0
- package/src/env/prompt/index.ts +18 -0
- package/src/env/prompt/memory-prompts.test.ts +91 -0
- package/src/env/prompt/prompt-component.test.ts +491 -0
- package/src/env/prompt/prompt-component.ts +619 -0
- package/src/env/prompt/prompt-config-registration.test.ts +213 -0
- package/src/env/prompt/prompt-config-registration.ts +39 -0
- package/src/env/prompt/prompts-index.ts +504 -0
- package/src/env/prompt/renderer.ts +67 -0
- package/src/env/prompt/types.ts +136 -0
- package/src/env/session/hooks.ts +18 -0
- package/src/env/session/index.ts +37 -0
- package/src/env/session/search-query-parser.test.ts +425 -0
- package/src/env/session/search-query-parser.ts +171 -0
- package/src/env/session/session-checkpoint.test.ts +523 -0
- package/src/env/session/session-component.extract-recent-messages.test.ts +209 -0
- package/src/env/session/session-component.test.ts +132 -0
- package/src/env/session/session-component.ts +1249 -0
- package/src/env/session/session-config-registration.test.ts +138 -0
- package/src/env/session/session-config-registration.ts +52 -0
- package/src/env/session/session-message-converter.test.ts +763 -0
- package/src/env/session/session-message-converter.ts +415 -0
- package/src/env/session/session-message-e2e.test.ts +448 -0
- package/src/env/session/session-search.test.ts +391 -0
- package/src/env/session/session-store.test.ts +362 -0
- package/src/env/session/session-store.ts +141 -0
- package/src/env/session/storage/index.ts +6 -0
- package/src/env/session/storage/memory.ts +502 -0
- package/src/env/session/storage/sqlite.ts +794 -0
- package/src/env/session/types.ts +742 -0
- package/src/env/skill/config.ts +39 -0
- package/src/env/skill/index.ts +6 -0
- package/src/env/skill/parser.test.ts +116 -0
- package/src/env/skill/parser.ts +77 -0
- package/src/env/skill/scanner.test.ts +211 -0
- package/src/env/skill/scanner.ts +119 -0
- package/src/env/skill/skill-component.test.ts +234 -0
- package/src/env/skill/skill-component.ts +352 -0
- package/src/env/skill/skill-config-registration.test.ts +60 -0
- package/src/env/skill/skill-config-registration.ts +43 -0
- package/src/env/skill/tool/index.ts +1 -0
- package/src/env/skill/tool/skill-tool.test.ts +100 -0
- package/src/env/skill/tool/skill-tool.ts +72 -0
- package/src/env/skill/types.ts +64 -0
- package/src/env/task/delegate/delegate-tool.test.ts +498 -0
- package/src/env/task/delegate/delegate-tool.ts +1014 -0
- package/src/env/task/delegate/index.ts +18 -0
- package/src/env/task/delegate/stop-tool.test.ts +140 -0
- package/src/env/task/delegate/stop-tool.ts +119 -0
- package/src/env/task/delegate/task-events.test.ts +178 -0
- package/src/env/task/delegate/task-events.ts +143 -0
- package/src/env/task/hooks/contexts.test.ts +92 -0
- package/src/env/task/hooks/contexts.ts +192 -0
- package/src/env/task/hooks/index.ts +23 -0
- package/src/env/task/hooks/task-hook-points.test.ts +32 -0
- package/src/env/task/hooks/task-hook-points.ts +54 -0
- package/src/env/task/index.ts +7 -0
- package/src/env/task/plugins/index.ts +13 -0
- package/src/env/task/plugins/task-plugin.test.ts +74 -0
- package/src/env/task/plugins/task-plugin.ts +89 -0
- package/src/env/task/plugins/task-tag-plugin.test.ts +377 -0
- package/src/env/task/plugins/task-tag-plugin.ts +319 -0
- package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +226 -0
- package/src/env/task/plugins/workflow-extractor-agent.test.ts +107 -0
- package/src/env/task/plugins/workflow-extractor-agent.ts +225 -0
- package/src/env/task/storage/index.ts +6 -0
- package/src/env/task/storage/sqlite-task-store.test.ts +283 -0
- package/src/env/task/storage/sqlite-task-store.ts +903 -0
- package/src/env/task/storage/task-search.test.ts +291 -0
- package/src/env/task/tag-service.test.ts +198 -0
- package/src/env/task/tag-service.ts +264 -0
- package/src/env/task/task-component.test.ts +193 -0
- package/src/env/task/task-component.ts +658 -0
- package/src/env/task/task-config-registration.test.ts +57 -0
- package/src/env/task/task-config-registration.ts +37 -0
- package/src/env/task/task-types.test.ts +137 -0
- package/src/env/task/tools/complete-tool.ts +44 -0
- package/src/env/task/tools/create-tool.ts +49 -0
- package/src/env/task/tools/delete-tool.ts +43 -0
- package/src/env/task/tools/get-tool.ts +59 -0
- package/src/env/task/tools/index.ts +10 -0
- package/src/env/task/tools/list-tool.ts +40 -0
- package/src/env/task/tools/operation/create-tool.ts +48 -0
- package/src/env/task/tools/operation/delete-tool.ts +43 -0
- package/src/env/task/tools/operation/get-tool.ts +43 -0
- package/src/env/task/tools/operation/index.ts +9 -0
- package/src/env/task/tools/operation/list-tool.ts +40 -0
- package/src/env/task/tools/operation/operation-tools.test.ts +274 -0
- package/src/env/task/tools/operation/operation-types.ts +75 -0
- package/src/env/task/tools/operation/update-tool.ts +47 -0
- package/src/env/task/tools/task-tools.test.ts +203 -0
- package/src/env/task/tools/task-types.test.ts +75 -0
- package/src/env/task/tools/task-types.ts +68 -0
- package/src/env/task/tools/update-tool.ts +70 -0
- package/src/env/task/types.ts +160 -0
- package/src/env/tool/built-in/bash.ts +201 -0
- package/src/env/tool/built-in/echo.ts +29 -0
- package/src/env/tool/built-in/edit-file.test.ts +136 -0
- package/src/env/tool/built-in/edit-file.ts +92 -0
- package/src/env/tool/built-in/glob.test.ts +94 -0
- package/src/env/tool/built-in/glob.ts +65 -0
- package/src/env/tool/built-in/grep.test.ts +122 -0
- package/src/env/tool/built-in/grep.ts +108 -0
- package/src/env/tool/built-in/index.ts +44 -0
- package/src/env/tool/built-in/read-file.test.ts +84 -0
- package/src/env/tool/built-in/read-file.ts +75 -0
- package/src/env/tool/built-in/write-file.test.ts +119 -0
- package/src/env/tool/built-in/write-file.ts +68 -0
- package/src/env/tool/index.ts +24 -0
- package/src/env/tool/registry.test.ts +257 -0
- package/src/env/tool/registry.ts +167 -0
- package/src/env/tool/tool-component.test.ts +559 -0
- package/src/env/tool/tool-component.ts +563 -0
- package/src/env/tool/tool-config-registration.test.ts +249 -0
- package/src/env/tool/tool-config-registration.ts +46 -0
- package/src/env/tool/types.ts +267 -0
- package/src/env/tool/validator.test.ts +143 -0
- package/src/env/tool/validator.ts +44 -0
- package/src/env/types.ts +180 -0
- package/src/env/workflow/ask-user-tool-registration.test.ts +216 -0
- package/src/env/workflow/complex-workflow.integration.test.ts +1900 -0
- package/src/env/workflow/decorators/decorator-node.ts +229 -0
- package/src/env/workflow/decorators/decorator.test.ts +196 -0
- package/src/env/workflow/decorators/edge.ts +82 -0
- package/src/env/workflow/decorators/index.ts +31 -0
- package/src/env/workflow/decorators/node-as.ts +98 -0
- package/src/env/workflow/decorators/workflow.ts +54 -0
- package/src/env/workflow/engine/dag-manager.test.ts +570 -0
- package/src/env/workflow/engine/dag-manager.ts +594 -0
- package/src/env/workflow/engine/engine.ts +1422 -0
- package/src/env/workflow/engine/event-bus.test.ts +359 -0
- package/src/env/workflow/engine/event-bus.ts +156 -0
- package/src/env/workflow/engine/executor-agent-session.test.ts +84 -0
- package/src/env/workflow/engine/executor.test.ts +619 -0
- package/src/env/workflow/engine/executor.ts +593 -0
- package/src/env/workflow/engine/index.ts +24 -0
- package/src/env/workflow/engine/node-registry.test.ts +560 -0
- package/src/env/workflow/engine/node-registry.ts +289 -0
- package/src/env/workflow/engine/resume-removed.test.ts +22 -0
- package/src/env/workflow/engine/scheduler.test.ts +715 -0
- package/src/env/workflow/engine/scheduler.ts +318 -0
- package/src/env/workflow/engine/workflow-engine.test.ts +815 -0
- package/src/env/workflow/extractor/workflow-converter.ts +306 -0
- package/src/env/workflow/fixtures.ts +380 -0
- package/src/env/workflow/index.ts +38 -0
- package/src/env/workflow/integration/run-resume-unified.test.ts +186 -0
- package/src/env/workflow/integration/service-integration.test.ts +267 -0
- package/src/env/workflow/metadata/keys.ts +12 -0
- package/src/env/workflow/nodes/agent-component-adapter.test.ts +318 -0
- package/src/env/workflow/nodes/agent-component-adapter.ts +448 -0
- package/src/env/workflow/nodes/agent-node.test.ts +371 -0
- package/src/env/workflow/nodes/agent-node.ts +598 -0
- package/src/env/workflow/nodes/ask-user-node.ts +113 -0
- package/src/env/workflow/nodes/condition-node.ts +200 -0
- package/src/env/workflow/nodes/index.ts +9 -0
- package/src/env/workflow/nodes/merge-node.ts +141 -0
- package/src/env/workflow/nodes/skill-node.test.ts +253 -0
- package/src/env/workflow/nodes/skill-node.ts +393 -0
- package/src/env/workflow/nodes/tool-node.test.ts +251 -0
- package/src/env/workflow/nodes/tool-node.ts +493 -0
- package/src/env/workflow/nodes/workflow-llm-history.test.ts +455 -0
- package/src/env/workflow/nodes/workflow-node.test.ts +315 -0
- package/src/env/workflow/nodes/workflow-node.ts +311 -0
- package/src/env/workflow/service/index.ts +27 -0
- package/src/env/workflow/service/registry.test.ts +133 -0
- package/src/env/workflow/service/registry.ts +71 -0
- package/src/env/workflow/service/workflow-service.test.ts +310 -0
- package/src/env/workflow/service/workflow-service.ts +393 -0
- package/src/env/workflow/storage/index.ts +28 -0
- package/src/env/workflow/storage/mock-repositories.ts +385 -0
- package/src/env/workflow/storage/sqlite.test.ts +179 -0
- package/src/env/workflow/storage/sqlite.ts +163 -0
- package/src/env/workflow/storage/workflow-repo.test.ts +780 -0
- package/src/env/workflow/storage/workflow-repo.ts +342 -0
- package/src/env/workflow/tools/ask-user-tool.ts +82 -0
- package/src/env/workflow/tools/index.ts +26 -0
- package/src/env/workflow/tools/run-workflow.test.ts +352 -0
- package/src/env/workflow/tools/run-workflow.ts +214 -0
- package/src/env/workflow/types/context.ts +18 -0
- package/src/env/workflow/types/decorators-types.ts +198 -0
- package/src/env/workflow/types/event.test.ts +515 -0
- package/src/env/workflow/types/event.ts +193 -0
- package/src/env/workflow/types/index.ts +49 -0
- package/src/env/workflow/types/run.test.ts +437 -0
- package/src/env/workflow/types/run.ts +173 -0
- package/src/env/workflow/types/workflow-hil.ts +114 -0
- package/src/env/workflow/types/workflow-message.test.ts +138 -0
- package/src/env/workflow/types/workflow-message.ts +196 -0
- package/src/env/workflow/types/workflow-session.test.ts +95 -0
- package/src/env/workflow/types/workflow-session.ts +59 -0
- package/src/env/workflow/types/workflow.test.ts +495 -0
- package/src/env/workflow/types/workflow.ts +195 -0
- package/src/env/workflow/types_compat.ts +51 -0
- package/src/env/workflow/utils/create-workflow.ts +47 -0
- package/src/env/workflow/utils/execution-state.ts +245 -0
- package/src/env/workflow/utils/index.ts +18 -0
- package/src/env/workflow/utils/node-registry-helper.ts +58 -0
- package/src/env/workflow/utils/recovery-validator.test.ts +460 -0
- package/src/env/workflow/utils/recovery-validator.ts +377 -0
- package/src/env/workflow/utils/session-parser.test.ts +111 -0
- package/src/env/workflow/utils/session-parser.ts +94 -0
- package/src/env/workflow/utils/session-recovery.test.ts +334 -0
- package/src/env/workflow/utils/session-recovery.ts +188 -0
- package/src/env/workflow/utils/template-resolver.test.ts +258 -0
- package/src/env/workflow/utils/template-resolver.ts +436 -0
- package/src/env/workflow/utils/validation-rules.ts +149 -0
- package/src/env/workflow/workflow-component.ts +544 -0
- package/src/index.ts +422 -0
- package/src/utils/id.ts +21 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { Scheduler, SchedulerOptions, SchedulerState } from './scheduler';
|
|
3
|
+
import { DAGManager } from './dag-manager';
|
|
4
|
+
import type { WorkflowDefinition, NodeDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
// Helper to create a basic workflow definition
|
|
7
|
+
function createWorkflow(nodes: NodeDefinition[], config?: { parallel_limit?: number | null }): WorkflowDefinition {
|
|
8
|
+
return {
|
|
9
|
+
name: 'test-workflow',
|
|
10
|
+
version: '1.0',
|
|
11
|
+
nodes,
|
|
12
|
+
entry: '__default_entry__',
|
|
13
|
+
config: config ? { parallel_limit: config.parallel_limit } : undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Helper to create a node definition
|
|
18
|
+
function createNode(id: string, dependsOn: string[] = []): NodeDefinition {
|
|
19
|
+
return {
|
|
20
|
+
id,
|
|
21
|
+
type: 'test',
|
|
22
|
+
depends_on: dependsOn,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('Scheduler', () => {
|
|
27
|
+
describe('constructor', () => {
|
|
28
|
+
it('should create a Scheduler instance with DAGManager', () => {
|
|
29
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
30
|
+
const dagManager = new DAGManager(workflow);
|
|
31
|
+
const scheduler = new Scheduler(dagManager);
|
|
32
|
+
|
|
33
|
+
expect(scheduler).toBeInstanceOf(Scheduler);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should create scheduler with default options', () => {
|
|
37
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
38
|
+
const dagManager = new DAGManager(workflow);
|
|
39
|
+
const scheduler = new Scheduler(dagManager);
|
|
40
|
+
|
|
41
|
+
expect(scheduler).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should create scheduler with custom parallel limit', () => {
|
|
45
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
46
|
+
const dagManager = new DAGManager(workflow);
|
|
47
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
|
|
48
|
+
|
|
49
|
+
expect(scheduler).toBeInstanceOf(Scheduler);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should support unlimited parallel (null limit)', () => {
|
|
53
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
54
|
+
const dagManager = new DAGManager(workflow);
|
|
55
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: null });
|
|
56
|
+
|
|
57
|
+
expect(scheduler).toBeInstanceOf(Scheduler);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('getReadyNodes()', () => {
|
|
62
|
+
it('should return entry nodes when nothing is completed', () => {
|
|
63
|
+
const workflow = createWorkflow([
|
|
64
|
+
createNode('A'),
|
|
65
|
+
createNode('B', ['A']),
|
|
66
|
+
createNode('C', ['B']),
|
|
67
|
+
]);
|
|
68
|
+
const dagManager = new DAGManager(workflow);
|
|
69
|
+
const scheduler = new Scheduler(dagManager);
|
|
70
|
+
|
|
71
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
72
|
+
expect(ready).toEqual(['A']);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return next nodes when dependencies are met', () => {
|
|
76
|
+
const workflow = createWorkflow([
|
|
77
|
+
createNode('A'),
|
|
78
|
+
createNode('B', ['A']),
|
|
79
|
+
createNode('C', ['B']),
|
|
80
|
+
]);
|
|
81
|
+
const dagManager = new DAGManager(workflow);
|
|
82
|
+
const scheduler = new Scheduler(dagManager);
|
|
83
|
+
|
|
84
|
+
const ready = scheduler.getReadyNodes(new Set(['A']));
|
|
85
|
+
expect(ready).toEqual(['B']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return multiple nodes for parallel workflow', () => {
|
|
89
|
+
const workflow = createWorkflow([
|
|
90
|
+
createNode('A'),
|
|
91
|
+
createNode('B'),
|
|
92
|
+
createNode('C'),
|
|
93
|
+
]);
|
|
94
|
+
const dagManager = new DAGManager(workflow);
|
|
95
|
+
const scheduler = new Scheduler(dagManager);
|
|
96
|
+
|
|
97
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
98
|
+
expect(ready).toHaveLength(3);
|
|
99
|
+
expect(ready).toContain('A');
|
|
100
|
+
expect(ready).toContain('B');
|
|
101
|
+
expect(ready).toContain('C');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return multiple nodes for diamond workflow after entry completes', () => {
|
|
105
|
+
// Diamond: A -> B -> D, A -> C -> D
|
|
106
|
+
const workflow = createWorkflow([
|
|
107
|
+
createNode('A'),
|
|
108
|
+
createNode('B', ['A']),
|
|
109
|
+
createNode('C', ['A']),
|
|
110
|
+
createNode('D', ['B', 'C']),
|
|
111
|
+
]);
|
|
112
|
+
const dagManager = new DAGManager(workflow);
|
|
113
|
+
const scheduler = new Scheduler(dagManager);
|
|
114
|
+
|
|
115
|
+
// After A is done, B and C should be ready
|
|
116
|
+
const readyAfterA = scheduler.getReadyNodes(new Set(['A']));
|
|
117
|
+
expect(readyAfterA).toContain('B');
|
|
118
|
+
expect(readyAfterA).toContain('C');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return empty array when all nodes are completed', () => {
|
|
122
|
+
const workflow = createWorkflow([
|
|
123
|
+
createNode('A'),
|
|
124
|
+
createNode('B', ['A']),
|
|
125
|
+
]);
|
|
126
|
+
const dagManager = new DAGManager(workflow);
|
|
127
|
+
const scheduler = new Scheduler(dagManager);
|
|
128
|
+
|
|
129
|
+
const ready = scheduler.getReadyNodes(new Set(['A', 'B']));
|
|
130
|
+
expect(ready).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not return already running nodes', () => {
|
|
134
|
+
const workflow = createWorkflow([
|
|
135
|
+
createNode('A'),
|
|
136
|
+
createNode('B'),
|
|
137
|
+
createNode('C'),
|
|
138
|
+
]);
|
|
139
|
+
const dagManager = new DAGManager(workflow);
|
|
140
|
+
const scheduler = new Scheduler(dagManager);
|
|
141
|
+
|
|
142
|
+
// Mark A as running
|
|
143
|
+
scheduler.markStarted('A');
|
|
144
|
+
|
|
145
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
146
|
+
expect(ready).not.toContain('A');
|
|
147
|
+
expect(ready).toContain('B');
|
|
148
|
+
expect(ready).toContain('C');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not return completed nodes', () => {
|
|
152
|
+
const workflow = createWorkflow([
|
|
153
|
+
createNode('A'),
|
|
154
|
+
createNode('B'),
|
|
155
|
+
createNode('C'),
|
|
156
|
+
]);
|
|
157
|
+
const dagManager = new DAGManager(workflow);
|
|
158
|
+
const scheduler = new Scheduler(dagManager);
|
|
159
|
+
|
|
160
|
+
// Mark A as completed
|
|
161
|
+
scheduler.markCompleted('A');
|
|
162
|
+
|
|
163
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
164
|
+
expect(ready).not.toContain('A');
|
|
165
|
+
expect(ready).toContain('B');
|
|
166
|
+
expect(ready).toContain('C');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('getRunningCount()', () => {
|
|
171
|
+
it('should return 0 initially', () => {
|
|
172
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
173
|
+
const dagManager = new DAGManager(workflow);
|
|
174
|
+
const scheduler = new Scheduler(dagManager);
|
|
175
|
+
|
|
176
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should return correct count after marking nodes as started', () => {
|
|
180
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
181
|
+
const dagManager = new DAGManager(workflow);
|
|
182
|
+
const scheduler = new Scheduler(dagManager);
|
|
183
|
+
|
|
184
|
+
scheduler.markStarted('A');
|
|
185
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
186
|
+
|
|
187
|
+
scheduler.markStarted('B');
|
|
188
|
+
expect(scheduler.getRunningCount()).toBe(2);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should return correct count after completing nodes', () => {
|
|
192
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
193
|
+
const dagManager = new DAGManager(workflow);
|
|
194
|
+
const scheduler = new Scheduler(dagManager);
|
|
195
|
+
|
|
196
|
+
scheduler.markStarted('A');
|
|
197
|
+
scheduler.markStarted('B');
|
|
198
|
+
expect(scheduler.getRunningCount()).toBe(2);
|
|
199
|
+
|
|
200
|
+
scheduler.markCompleted('A');
|
|
201
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('canStartMore()', () => {
|
|
206
|
+
it('should return true when under parallel limit', () => {
|
|
207
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
208
|
+
const dagManager = new DAGManager(workflow);
|
|
209
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 3 });
|
|
210
|
+
|
|
211
|
+
scheduler.markStarted('A');
|
|
212
|
+
scheduler.markStarted('B');
|
|
213
|
+
|
|
214
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should return false when at parallel limit', () => {
|
|
218
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
219
|
+
const dagManager = new DAGManager(workflow);
|
|
220
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
|
|
221
|
+
|
|
222
|
+
scheduler.markStarted('A');
|
|
223
|
+
scheduler.markStarted('B');
|
|
224
|
+
|
|
225
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should return true when parallel limit is null (unlimited)', () => {
|
|
229
|
+
const workflow = createWorkflow([
|
|
230
|
+
createNode('A'),
|
|
231
|
+
createNode('B'),
|
|
232
|
+
createNode('C'),
|
|
233
|
+
createNode('D'),
|
|
234
|
+
createNode('E'),
|
|
235
|
+
]);
|
|
236
|
+
const dagManager = new DAGManager(workflow);
|
|
237
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: null });
|
|
238
|
+
|
|
239
|
+
// Start many nodes
|
|
240
|
+
scheduler.markStarted('A');
|
|
241
|
+
scheduler.markStarted('B');
|
|
242
|
+
scheduler.markStarted('C');
|
|
243
|
+
scheduler.markStarted('D');
|
|
244
|
+
scheduler.markStarted('E');
|
|
245
|
+
|
|
246
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return true when single-threaded and no nodes running', () => {
|
|
250
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
251
|
+
const dagManager = new DAGManager(workflow);
|
|
252
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
|
|
253
|
+
|
|
254
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should return false when single-threaded and one node running', () => {
|
|
258
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
259
|
+
const dagManager = new DAGManager(workflow);
|
|
260
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
|
|
261
|
+
|
|
262
|
+
scheduler.markStarted('A');
|
|
263
|
+
|
|
264
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('markStarted()', () => {
|
|
269
|
+
it('should track running node', () => {
|
|
270
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
271
|
+
const dagManager = new DAGManager(workflow);
|
|
272
|
+
const scheduler = new Scheduler(dagManager);
|
|
273
|
+
|
|
274
|
+
scheduler.markStarted('A');
|
|
275
|
+
|
|
276
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
277
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should allow starting more when under limit', () => {
|
|
281
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
282
|
+
const dagManager = new DAGManager(workflow);
|
|
283
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 3 });
|
|
284
|
+
|
|
285
|
+
scheduler.markStarted('A');
|
|
286
|
+
scheduler.markStarted('B');
|
|
287
|
+
|
|
288
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('markCompleted()', () => {
|
|
293
|
+
it('should remove node from running set', () => {
|
|
294
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
295
|
+
const dagManager = new DAGManager(workflow);
|
|
296
|
+
const scheduler = new Scheduler(dagManager);
|
|
297
|
+
|
|
298
|
+
scheduler.markStarted('A');
|
|
299
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
300
|
+
|
|
301
|
+
scheduler.markCompleted('A');
|
|
302
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should allow starting more after completion', () => {
|
|
306
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
307
|
+
const dagManager = new DAGManager(workflow);
|
|
308
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
|
|
309
|
+
|
|
310
|
+
scheduler.markStarted('A');
|
|
311
|
+
scheduler.markStarted('B');
|
|
312
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
313
|
+
|
|
314
|
+
scheduler.markCompleted('A');
|
|
315
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should handle completing a node that was not started', () => {
|
|
319
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
320
|
+
const dagManager = new DAGManager(workflow);
|
|
321
|
+
const scheduler = new Scheduler(dagManager);
|
|
322
|
+
|
|
323
|
+
// Should not throw
|
|
324
|
+
scheduler.markCompleted('A');
|
|
325
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('markFailed()', () => {
|
|
330
|
+
it('should remove node from running set on failure', () => {
|
|
331
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
332
|
+
const dagManager = new DAGManager(workflow);
|
|
333
|
+
const scheduler = new Scheduler(dagManager);
|
|
334
|
+
|
|
335
|
+
scheduler.markStarted('A');
|
|
336
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
337
|
+
|
|
338
|
+
scheduler.markFailed('A');
|
|
339
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should track failed nodes in state', () => {
|
|
343
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
344
|
+
const dagManager = new DAGManager(workflow);
|
|
345
|
+
const scheduler = new Scheduler(dagManager);
|
|
346
|
+
|
|
347
|
+
scheduler.markFailed('A');
|
|
348
|
+
|
|
349
|
+
const state = scheduler.getState();
|
|
350
|
+
expect(state.failed).toContain('A');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should not return failed node in ready nodes', () => {
|
|
354
|
+
const workflow = createWorkflow([createNode('A'), createNode('B', ['A'])]);
|
|
355
|
+
const dagManager = new DAGManager(workflow);
|
|
356
|
+
const scheduler = new Scheduler(dagManager);
|
|
357
|
+
|
|
358
|
+
scheduler.markFailed('A');
|
|
359
|
+
|
|
360
|
+
// A should not be ready since it failed
|
|
361
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
362
|
+
expect(ready).not.toContain('A');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('reset()', () => {
|
|
367
|
+
it('should clear all running, completed, and failed states', () => {
|
|
368
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
369
|
+
const dagManager = new DAGManager(workflow);
|
|
370
|
+
const scheduler = new Scheduler(dagManager);
|
|
371
|
+
|
|
372
|
+
// Set up some state
|
|
373
|
+
scheduler.markStarted('A');
|
|
374
|
+
scheduler.markStarted('B');
|
|
375
|
+
scheduler.markCompleted('A');
|
|
376
|
+
scheduler.markFailed('C');
|
|
377
|
+
|
|
378
|
+
// Reset
|
|
379
|
+
scheduler.reset();
|
|
380
|
+
|
|
381
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
382
|
+
|
|
383
|
+
const state = scheduler.getState();
|
|
384
|
+
expect(state.running).toHaveLength(0);
|
|
385
|
+
expect(state.completed).toHaveLength(0);
|
|
386
|
+
expect(state.failed).toHaveLength(0);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should allow starting fresh after reset', () => {
|
|
390
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
391
|
+
const dagManager = new DAGManager(workflow);
|
|
392
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
|
|
393
|
+
|
|
394
|
+
scheduler.markStarted('A');
|
|
395
|
+
scheduler.markCompleted('A');
|
|
396
|
+
scheduler.reset();
|
|
397
|
+
|
|
398
|
+
// Should be able to start again
|
|
399
|
+
scheduler.markStarted('A');
|
|
400
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('getState()', () => {
|
|
405
|
+
it('should return current scheduler state', () => {
|
|
406
|
+
const workflow = createWorkflow([
|
|
407
|
+
createNode('A'),
|
|
408
|
+
createNode('B', ['A']),
|
|
409
|
+
createNode('C', ['A']),
|
|
410
|
+
createNode('D', ['B', 'C']),
|
|
411
|
+
]);
|
|
412
|
+
const dagManager = new DAGManager(workflow);
|
|
413
|
+
const scheduler = new Scheduler(dagManager);
|
|
414
|
+
|
|
415
|
+
const state = scheduler.getState();
|
|
416
|
+
|
|
417
|
+
expect(state).toHaveProperty('pending');
|
|
418
|
+
expect(state).toHaveProperty('ready');
|
|
419
|
+
expect(state).toHaveProperty('running');
|
|
420
|
+
expect(state).toHaveProperty('completed');
|
|
421
|
+
expect(state).toHaveProperty('failed');
|
|
422
|
+
expect(state).toHaveProperty('skipped');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should track pending nodes correctly', () => {
|
|
426
|
+
const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
|
|
427
|
+
const dagManager = new DAGManager(workflow);
|
|
428
|
+
const scheduler = new Scheduler(dagManager);
|
|
429
|
+
|
|
430
|
+
const state = scheduler.getState();
|
|
431
|
+
|
|
432
|
+
// All nodes should be pending initially
|
|
433
|
+
expect(state.pending).toHaveLength(3);
|
|
434
|
+
expect(state.pending).toContain('A');
|
|
435
|
+
expect(state.pending).toContain('B');
|
|
436
|
+
expect(state.pending).toContain('C');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should track ready nodes correctly', () => {
|
|
440
|
+
const workflow = createWorkflow([createNode('A'), createNode('B', ['A'])]);
|
|
441
|
+
const dagManager = new DAGManager(workflow);
|
|
442
|
+
const scheduler = new Scheduler(dagManager);
|
|
443
|
+
|
|
444
|
+
const state = scheduler.getState();
|
|
445
|
+
|
|
446
|
+
// A should be ready (entry node)
|
|
447
|
+
expect(state.ready).toContain('A');
|
|
448
|
+
// B should not be ready yet (depends on A)
|
|
449
|
+
expect(state.ready).not.toContain('B');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should reflect state changes after operations', () => {
|
|
453
|
+
const workflow = createWorkflow([createNode('A'), createNode('B')]);
|
|
454
|
+
const dagManager = new DAGManager(workflow);
|
|
455
|
+
const scheduler = new Scheduler(dagManager);
|
|
456
|
+
|
|
457
|
+
scheduler.markStarted('A');
|
|
458
|
+
scheduler.markCompleted('A');
|
|
459
|
+
scheduler.markStarted('B');
|
|
460
|
+
|
|
461
|
+
const state = scheduler.getState();
|
|
462
|
+
|
|
463
|
+
expect(state.running).toContain('B');
|
|
464
|
+
expect(state.completed).toContain('A');
|
|
465
|
+
expect(state.pending).not.toContain('A');
|
|
466
|
+
expect(state.pending).not.toContain('B');
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe('parallel execution scenarios', () => {
|
|
471
|
+
it('should respect parallel limit of 2', () => {
|
|
472
|
+
const workflow = createWorkflow([
|
|
473
|
+
createNode('A'),
|
|
474
|
+
createNode('B'),
|
|
475
|
+
createNode('C'),
|
|
476
|
+
createNode('D'),
|
|
477
|
+
]);
|
|
478
|
+
const dagManager = new DAGManager(workflow);
|
|
479
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
|
|
480
|
+
|
|
481
|
+
// Start first two nodes
|
|
482
|
+
scheduler.markStarted('A');
|
|
483
|
+
scheduler.markStarted('B');
|
|
484
|
+
|
|
485
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
486
|
+
|
|
487
|
+
// Complete one
|
|
488
|
+
scheduler.markCompleted('A');
|
|
489
|
+
|
|
490
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
491
|
+
|
|
492
|
+
// Start third
|
|
493
|
+
scheduler.markStarted('C');
|
|
494
|
+
expect(scheduler.canStartMore()).toBe(false);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('should handle single-threaded execution', () => {
|
|
498
|
+
const workflow = createWorkflow([
|
|
499
|
+
createNode('A'),
|
|
500
|
+
createNode('B', ['A']),
|
|
501
|
+
createNode('C', ['B']),
|
|
502
|
+
]);
|
|
503
|
+
const dagManager = new DAGManager(workflow);
|
|
504
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
|
|
505
|
+
|
|
506
|
+
// Initially only A is ready
|
|
507
|
+
let ready = scheduler.getReadyNodes(new Set());
|
|
508
|
+
expect(ready).toEqual(['A']);
|
|
509
|
+
|
|
510
|
+
// Start A
|
|
511
|
+
scheduler.markStarted('A');
|
|
512
|
+
|
|
513
|
+
// B should not be in ready (A not completed)
|
|
514
|
+
ready = scheduler.getReadyNodes(new Set());
|
|
515
|
+
expect(ready).not.toContain('B');
|
|
516
|
+
|
|
517
|
+
// Complete A
|
|
518
|
+
scheduler.markCompleted('A');
|
|
519
|
+
|
|
520
|
+
// Now B should be ready
|
|
521
|
+
ready = scheduler.getReadyNodes(new Set(['A']));
|
|
522
|
+
expect(ready).toEqual(['B']);
|
|
523
|
+
|
|
524
|
+
scheduler.markStarted('B');
|
|
525
|
+
scheduler.markCompleted('B');
|
|
526
|
+
|
|
527
|
+
// Now C should be ready
|
|
528
|
+
ready = scheduler.getReadyNodes(new Set(['A', 'B']));
|
|
529
|
+
expect(ready).toEqual(['C']);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should allow unlimited parallel execution', () => {
|
|
533
|
+
const workflow = createWorkflow([
|
|
534
|
+
createNode('A'),
|
|
535
|
+
createNode('B'),
|
|
536
|
+
createNode('C'),
|
|
537
|
+
createNode('D'),
|
|
538
|
+
createNode('E'),
|
|
539
|
+
]);
|
|
540
|
+
const dagManager = new DAGManager(workflow);
|
|
541
|
+
const scheduler = new Scheduler(dagManager, { parallelLimit: null });
|
|
542
|
+
|
|
543
|
+
// Start all nodes
|
|
544
|
+
scheduler.markStarted('A');
|
|
545
|
+
scheduler.markStarted('B');
|
|
546
|
+
scheduler.markStarted('C');
|
|
547
|
+
scheduler.markStarted('D');
|
|
548
|
+
scheduler.markStarted('E');
|
|
549
|
+
|
|
550
|
+
expect(scheduler.canStartMore()).toBe(true);
|
|
551
|
+
expect(scheduler.getRunningCount()).toBe(5);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
describe('complex DAG scenarios', () => {
|
|
556
|
+
it('should handle diamond DAG correctly', () => {
|
|
557
|
+
// Diamond: A -> B -> D, A -> C -> D
|
|
558
|
+
const workflow = createWorkflow([
|
|
559
|
+
createNode('A'),
|
|
560
|
+
createNode('B', ['A']),
|
|
561
|
+
createNode('C', ['A']),
|
|
562
|
+
createNode('D', ['B', 'C']),
|
|
563
|
+
]);
|
|
564
|
+
const dagManager = new DAGManager(workflow);
|
|
565
|
+
const scheduler = new Scheduler(dagManager);
|
|
566
|
+
|
|
567
|
+
// Initially only A is ready
|
|
568
|
+
let ready = scheduler.getReadyNodes(new Set());
|
|
569
|
+
expect(ready).toEqual(['A']);
|
|
570
|
+
|
|
571
|
+
// Complete A, B and C should be ready
|
|
572
|
+
scheduler.markStarted('A');
|
|
573
|
+
scheduler.markCompleted('A');
|
|
574
|
+
|
|
575
|
+
ready = scheduler.getReadyNodes(new Set(['A']));
|
|
576
|
+
expect(ready).toContain('B');
|
|
577
|
+
expect(ready).toContain('C');
|
|
578
|
+
|
|
579
|
+
// Complete B and C, D should be ready
|
|
580
|
+
scheduler.markStarted('B');
|
|
581
|
+
scheduler.markCompleted('B');
|
|
582
|
+
scheduler.markStarted('C');
|
|
583
|
+
scheduler.markCompleted('C');
|
|
584
|
+
|
|
585
|
+
ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C']));
|
|
586
|
+
expect(ready).toEqual(['D']);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('should handle complex DAG with multiple paths', () => {
|
|
590
|
+
// Complex workflow:
|
|
591
|
+
// A -> B -> D
|
|
592
|
+
// A -> C -> E
|
|
593
|
+
// D -> F
|
|
594
|
+
// E -> F
|
|
595
|
+
const workflow = createWorkflow([
|
|
596
|
+
createNode('A'),
|
|
597
|
+
createNode('B', ['A']),
|
|
598
|
+
createNode('C', ['A']),
|
|
599
|
+
createNode('D', ['B']),
|
|
600
|
+
createNode('E', ['C']),
|
|
601
|
+
createNode('F', ['D', 'E']),
|
|
602
|
+
]);
|
|
603
|
+
const dagManager = new DAGManager(workflow);
|
|
604
|
+
const scheduler = new Scheduler(dagManager);
|
|
605
|
+
|
|
606
|
+
// A is ready first
|
|
607
|
+
let ready = scheduler.getReadyNodes(new Set());
|
|
608
|
+
expect(ready).toEqual(['A']);
|
|
609
|
+
|
|
610
|
+
// Complete A, B and C should be ready
|
|
611
|
+
scheduler.markStarted('A');
|
|
612
|
+
scheduler.markCompleted('A');
|
|
613
|
+
|
|
614
|
+
ready = scheduler.getReadyNodes(new Set(['A']));
|
|
615
|
+
expect(ready).toContain('B');
|
|
616
|
+
expect(ready).toContain('C');
|
|
617
|
+
|
|
618
|
+
// Complete B and C
|
|
619
|
+
scheduler.markStarted('B');
|
|
620
|
+
scheduler.markCompleted('B');
|
|
621
|
+
scheduler.markStarted('C');
|
|
622
|
+
scheduler.markCompleted('C');
|
|
623
|
+
|
|
624
|
+
// D and E should be ready
|
|
625
|
+
ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C']));
|
|
626
|
+
expect(ready).toContain('D');
|
|
627
|
+
expect(ready).toContain('E');
|
|
628
|
+
|
|
629
|
+
// Complete D and E
|
|
630
|
+
scheduler.markStarted('D');
|
|
631
|
+
scheduler.markCompleted('D');
|
|
632
|
+
scheduler.markStarted('E');
|
|
633
|
+
scheduler.markCompleted('E');
|
|
634
|
+
|
|
635
|
+
// F should be ready
|
|
636
|
+
ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C', 'D', 'E']));
|
|
637
|
+
expect(ready).toEqual(['F']);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it('should handle multiple entry nodes', () => {
|
|
641
|
+
const workflow = createWorkflow([
|
|
642
|
+
createNode('A'),
|
|
643
|
+
createNode('B'),
|
|
644
|
+
createNode('C', ['A', 'B']),
|
|
645
|
+
]);
|
|
646
|
+
const dagManager = new DAGManager(workflow);
|
|
647
|
+
const scheduler = new Scheduler(dagManager);
|
|
648
|
+
|
|
649
|
+
// A and B should be ready
|
|
650
|
+
let ready = scheduler.getReadyNodes(new Set());
|
|
651
|
+
expect(ready).toContain('A');
|
|
652
|
+
expect(ready).toContain('B');
|
|
653
|
+
|
|
654
|
+
// Complete A only, C should not be ready
|
|
655
|
+
scheduler.markStarted('A');
|
|
656
|
+
scheduler.markCompleted('A');
|
|
657
|
+
|
|
658
|
+
ready = scheduler.getReadyNodes(new Set(['A']));
|
|
659
|
+
expect(ready).not.toContain('C');
|
|
660
|
+
|
|
661
|
+
// Complete B, C should now be ready
|
|
662
|
+
scheduler.markStarted('B');
|
|
663
|
+
scheduler.markCompleted('B');
|
|
664
|
+
|
|
665
|
+
ready = scheduler.getReadyNodes(new Set(['A', 'B']));
|
|
666
|
+
expect(ready).toEqual(['C']);
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
describe('edge cases', () => {
|
|
671
|
+
it('should handle single node workflow', () => {
|
|
672
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
673
|
+
const dagManager = new DAGManager(workflow);
|
|
674
|
+
const scheduler = new Scheduler(dagManager);
|
|
675
|
+
|
|
676
|
+
const ready = scheduler.getReadyNodes(new Set());
|
|
677
|
+
expect(ready).toEqual(['A']);
|
|
678
|
+
|
|
679
|
+
scheduler.markStarted('A');
|
|
680
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
681
|
+
|
|
682
|
+
scheduler.markCompleted('A');
|
|
683
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
684
|
+
|
|
685
|
+
const finalReady = scheduler.getReadyNodes(new Set(['A']));
|
|
686
|
+
expect(finalReady).toEqual([]);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it('should handle node marked as started multiple times', () => {
|
|
690
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
691
|
+
const dagManager = new DAGManager(workflow);
|
|
692
|
+
const scheduler = new Scheduler(dagManager);
|
|
693
|
+
|
|
694
|
+
// Mark as started multiple times
|
|
695
|
+
scheduler.markStarted('A');
|
|
696
|
+
scheduler.markStarted('A');
|
|
697
|
+
scheduler.markStarted('A');
|
|
698
|
+
|
|
699
|
+
// Should only count as one
|
|
700
|
+
expect(scheduler.getRunningCount()).toBe(1);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should handle node completion after failure', () => {
|
|
704
|
+
const workflow = createWorkflow([createNode('A')]);
|
|
705
|
+
const dagManager = new DAGManager(workflow);
|
|
706
|
+
const scheduler = new Scheduler(dagManager);
|
|
707
|
+
|
|
708
|
+
scheduler.markFailed('A');
|
|
709
|
+
scheduler.markCompleted('A');
|
|
710
|
+
|
|
711
|
+
// Should not throw, running count should be 0
|
|
712
|
+
expect(scheduler.getRunningCount()).toBe(0);
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
});
|