| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- import fs from 'fs/promises';
- import path from 'path';
- import { WorkflowExecutor } from './src/vl/workflow-executor.js';
- let passed = 0;
- let failed = 0;
- function test(name, fn) {
- return Promise.resolve()
- .then(fn)
- .then(() => {
- console.log(` ✓ ${name}`);
- passed++;
- })
- .catch((err) => {
- console.log(` ✗ ${name}: ${err.message}`);
- failed++;
- });
- }
- function assert(cond, msg) {
- if (!cond) throw new Error(msg || 'Assertion failed');
- }
- function assertIncludes(list, value, msg) {
- if (!list.includes(value)) {
- throw new Error(msg || `Expected ${JSON.stringify(value)} in ${JSON.stringify(list)}`);
- }
- }
- function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- function json(obj) {
- return JSON.stringify(obj);
- }
- function normalizeMessages(messages = []) {
- return messages.map((msg) => ({
- role: msg.role,
- content: msg.content,
- text: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
- }));
- }
- function findObjectMessage(messages, predicate) {
- for (const msg of messages) {
- const value = msg.content;
- if (value && typeof value === 'object' && !Array.isArray(value) && predicate(value)) {
- return value;
- }
- }
- return null;
- }
- function createFixture(projectName = 'SmokeApp') {
- const services = [
- {
- id: 'ItemService',
- domainId: 'ItemService',
- filePath: 'Services/ItemService.vs',
- path: 'Services/ItemService.vs',
- methods: [{ id: 'listItems', isPublic: true, params: '', returnType: '[OBJECT]' }],
- },
- {
- id: 'OrderService',
- domainId: 'OrderService',
- filePath: 'Services/OrderService.vs',
- path: 'Services/OrderService.vs',
- methods: [{ id: 'listOrders', isPublic: true, params: '', returnType: '[OBJECT]' }],
- },
- ];
- const components = [
- { id: 'ItemCard', filePath: 'ExtComponents/ItemCard.cp', path: 'ExtComponents/ItemCard.cp' },
- { id: 'OrderBadge', filePath: 'ExtComponents/OrderBadge.cp', path: 'ExtComponents/OrderBadge.cp' },
- ];
- const sections = [
- {
- id: 'HomePage',
- filePath: 'Sections/HomePage.sc',
- path: 'Sections/HomePage.sc',
- consumesServices: ['ItemService'],
- usesComponents: ['ItemCard'],
- },
- {
- id: 'OrdersPage',
- filePath: 'Sections/OrdersPage.sc',
- path: 'Sections/OrdersPage.sc',
- consumesServices: ['OrderService'],
- usesComponents: ['OrderBadge'],
- },
- ];
- const apps = [
- { id: 'MainApp', appId: 'MainApp', filePath: 'Apps/MainApp.vx', path: 'Apps/MainApp.vx', routes: [{ path: '/', section: 'HomePage' }] },
- { id: 'AdminApp', appId: 'AdminApp', filePath: 'Apps/AdminPortalShell.vx', path: 'Apps/AdminPortalShell.vx', routes: [{ path: '/orders', section: 'OrdersPage' }] },
- ];
- const fileManifest = {
- databaseFile: { id: 'MainDB', path: `Database/${projectName}.vdb`, type: 'vdb', sourceId: 'MainDB' },
- themeFile: { id: 'Theme', path: 'Theme/Theme.vth', type: 'vth', sourceId: 'Theme' },
- serviceFiles: services.map(({ id, path: filePath }) => ({ id, path: filePath, type: 'vs', sourceId: id })),
- componentFiles: components.map(({ id, path: filePath }) => ({ id, path: filePath, type: 'cp', sourceId: id })),
- sectionFiles: sections.map(({ id, path: filePath }) => ({ id, path: filePath, type: 'sc', sourceId: id })),
- appFiles: apps.map(({ id, appId, path: filePath }) => ({ id, appId, path: filePath, type: 'vx', sourceId: id })),
- };
- fileManifest.files = [
- fileManifest.databaseFile,
- fileManifest.themeFile,
- ...fileManifest.serviceFiles,
- ...fileManifest.componentFiles,
- ...fileManifest.sectionFiles,
- ...fileManifest.appFiles,
- ];
- fileManifest.references = [
- { from: 'Sections/HomePage.sc', to: 'Services/ItemService.vs', refType: 'consumes' },
- { from: 'Sections/OrdersPage.sc', to: 'Services/OrderService.vs', refType: 'consumes' },
- { from: 'Apps/MainApp.vx', to: 'Sections/HomePage.sc', refType: 'route' },
- { from: 'Apps/AdminApp.vx', to: 'Sections/OrdersPage.sc', refType: 'route' },
- ];
- return {
- prdJson: {
- projectName,
- summary: 'parallel smoke test',
- targetLang: 'zh-CN',
- },
- projectMeta: {
- specVersion: 'ProjectMeta/1.0',
- projectName,
- projectDescription: 'parallel smoke test',
- vlVersion: '3.5',
- config: {
- defaultDevice: 'Phone',
- defaultResolution: '375x812',
- themeFile: 'Theme/Theme.vth',
- colorScheme: 'light',
- },
- database: {
- file: `Database/${projectName}.vdb`,
- },
- theme: {
- file: 'Theme/Theme.vth',
- },
- dataSchema: {
- tables: [
- { id: 'Item', fields: [{ name: 'id', type: 'INT', constraints: ['PK'] }, { name: 'title', type: 'STRING' }] },
- { id: 'Order', fields: [{ name: 'id', type: 'INT', constraints: ['PK'] }, { name: 'status', type: 'STRING' }] },
- ],
- relations: [],
- },
- services: services.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- components: components.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- sections: sections.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- apps: apps.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- },
- blueprint: {
- projectName,
- valueDomains: { orderStatus: ['new', 'paid'] },
- roles: [{ id: 'Admin' }, { id: 'User' }],
- entities: [{ id: 'Item' }, { id: 'Order' }],
- apps: apps.map(({ appId, routes }) => ({ appId, pages: routes.map((route) => ({ id: route.section, sectionId: route.section })) })),
- features: ['list-items', 'list-orders'],
- },
- dataSchema: {
- tables: [
- { id: 'Item', fields: [{ name: 'id', type: 'INT' }, { name: 'title', type: 'STRING' }], indexes: [], testData: [] },
- { id: 'Order', fields: [{ name: 'id', type: 'INT' }, { name: 'status', type: 'STRING' }], indexes: [], testData: [] },
- ],
- relations: [],
- },
- layoutPlan: {
- apps: apps.map(({ appId, routes, path: filePath }) => ({
- appId,
- filePath,
- routes,
- layoutTree: { type: 'stack', items: routes.map((route) => route.section) },
- })),
- sections: sections.map(({ id }) => ({ id, structure: ['Header', 'List'] })),
- components: components.map(({ id }) => ({ id, previewSize: 'medium' })),
- },
- contractSheet: {
- serviceDomains: services.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- sectionContracts: sections.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- componentContracts: components.map(({ path: filePath, ...rest }) => ({ ...rest, filePath })),
- },
- stateBehavior: {
- appStates: apps.map(({ appId }) => ({ appId, globalVars: ['$ready(BOOL)'], initSequence: ['load'] })),
- sectionBehaviors: sections.map(({ id }) => ({ id, keyStates: ['$items([OBJECT])'], serviceBindings: [], initBehavior: [], internalMethods: [] })),
- eventWiring: [],
- modalTriggers: [],
- },
- intentSpec: {
- projectName,
- summary: 'parallel smoke test',
- domains: ['Item', 'Order'],
- project: { customTheme: true },
- apps: apps.map(({ appId }) => ({ appId })),
- },
- domainSchema: {
- entities: [
- { id: 'Item', fields: [{ name: 'id', type: 'INT' }, { name: 'title', type: 'STRING' }] },
- { id: 'Order', fields: [{ name: 'id', type: 'INT' }, { name: 'status', type: 'STRING' }] },
- ],
- relations: [],
- },
- serviceContractMap: {
- services: services.map(({ id, domainId, path: filePath }) => ({ id, domainId, path: filePath, filePath })),
- },
- serviceMapJson: {
- serviceDomains: services.map(({ id, domainId, path: filePath }) => ({ id, domainId, filePath })),
- },
- uiContractMap: {
- components: components.map(({ id, path: filePath }) => ({ id, path: filePath, filePath })),
- sections: sections.map(({ id, path: filePath }) => ({ id, path: filePath, filePath })),
- apps: apps.map(({ appId, path: filePath }) => ({ appId, path: filePath })),
- },
- uiMapJson: {
- components: components.map(({ id, path: filePath }) => ({ id, filePath })),
- sections: sections.map(({ id, path: filePath }) => ({ id, filePath })),
- apps: apps.map(({ id, appId, path: filePath }) => ({ id, appId, filePath })),
- },
- moduleManifest: {
- databaseFile: { id: 'MainDB', path: `Database/${projectName}.vdb`, type: 'vdb' },
- themeFile: { id: `${projectName}Theme`, path: `Theme/${projectName}.vth`, type: 'vth' },
- serviceFiles: services.map(({ id, domainId, path: filePath }) => ({ id, domainId, path: filePath, type: 'vs' })),
- componentFiles: components.map(({ id, path: filePath }) => ({ id, path: filePath, type: 'cp' })),
- sectionFiles: sections.map(({ id, path: filePath }) => ({ id, path: filePath, type: 'sc' })),
- appFiles: apps.map(({ id, appId, path: filePath }) => ({ id, appId, path: filePath, type: 'vx' })),
- files: fileManifest.files,
- references: fileManifest.references,
- },
- dependencyGraph: {
- nodes: [
- { id: 'ItemService', type: 'service' },
- { id: 'OrderService', type: 'service' },
- { id: 'HomePage', type: 'section' },
- { id: 'OrdersPage', type: 'section' },
- ],
- edges: [
- { from: 'HomePage', to: 'ItemService' },
- { from: 'OrdersPage', to: 'OrderService' },
- ],
- },
- qualityGateSpec: {
- rules: [{ id: 'no-missing-files', severity: 'error' }],
- },
- executionPlan: {
- tasks: [
- { id: 'gen-db', type: 'database' },
- { id: 'gen-theme', type: 'theme' },
- { id: 'gen-services', type: 'parallel' },
- ],
- parallelGroups: [['gen-db', 'gen-theme', 'gen-services']],
- },
- fileManifest,
- vdbCode: '// VL_VERSION:3.5\nDATABASE main\nTABLE Item { id INT, title STRING }\nTABLE Order { id INT, status STRING }\n',
- vthCode: `// VL_VERSION:3.5\nTHEME ${projectName} {\n COLOR primary = #006B5F\n}\n`,
- };
- }
- function serviceCode(id) {
- return `// VL_VERSION:3.5\nSERVICE ${id} {\n METHOD list(): [OBJECT]\n}\n`;
- }
- function componentCode(id) {
- return `// VL_VERSION:3.5\nCOMPONENT ${id} {\n VIEW { Text("${id}") }\n}\n`;
- }
- function sectionCode(id) {
- return `// VL_VERSION:3.5\nSECTION ${id} {\n INIT { }\n}\n`;
- }
- function appCode(id) {
- return `// VL_VERSION:3.5\nAPP ${id} {\n ROUTES { }\n}\n`;
- }
- function createFakeLLM(fixture, probe, options = {}) {
- return {
- async call(params) {
- const messages = normalizeMessages(params.messages);
- const allText = messages.map((msg) => msg.text).join('\n');
- const target = findObjectMessage(messages, (value) => (
- value.domainId || value.filePath || value.path || value.id || value.appId
- ));
- probe.active++;
- probe.maxActive = Math.max(probe.maxActive, probe.active);
- try {
- await sleep(options.delayMs || 80);
- const failId = options.failId;
- const targetId = target?.domainId || target?.id || target?.appId || target?.path || target?.filePath || '';
- if (failId && targetId.includes(failId)) {
- throw new Error(`forced failure for ${failId}`);
- }
- if (allText.includes('Generate the complete ProjectMeta JSON')) {
- return { content: json(fixture.projectMeta), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Requirement:') && allText.includes('Target Language:')) {
- return { content: json(fixture.prdJson), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Blueprint.json')) {
- return { content: json(fixture.blueprint), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate DataSchema.json')) {
- return { content: json(fixture.dataSchema), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate LayoutPlan.json')) {
- return { content: json(fixture.layoutPlan), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate FileManifest.json')) {
- return { content: json(fixture.fileManifest), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate ContractSheet.json')) {
- return { content: json(fixture.contractSheet), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate StateBehavior.json')) {
- return { content: json(fixture.stateBehavior), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/IntentSpec.json')) {
- return { content: json(fixture.intentSpec), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/DomainSchema.json')) {
- return { content: json(fixture.domainSchema), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/ServiceContractMap.json')) {
- return { content: json(fixture.serviceContractMap), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/QualityGateSpec.json')) {
- return { content: json(fixture.qualityGateSpec), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/UIContractMap.json')) {
- return { content: json(fixture.uiContractMap), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/ModuleManifest.json')) {
- return { content: json(fixture.moduleManifest), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Specs/DependencyGraph.json')) {
- return { content: json(fixture.dependencyGraph), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate Process/Run/ExecutionPlan.json')) {
- return { content: json(fixture.executionPlan), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('PRD:') && !allText.includes('VDB:') && !allText.includes('ServiceMap:')) {
- return { content: fixture.vdbCode, model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('PRD:') && allText.includes('VDB:')) {
- return { content: json(fixture.serviceMapJson), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('PRD:') && allText.includes('ServiceMap:')) {
- return { content: json(fixture.uiMapJson), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate the .vdb database schema file') || allText.includes('Generate complete VL .vdb source code')) {
- return { content: fixture.vdbCode, model: 'fake-parallel-smoke', usage: {} };
- }
- if (
- allText.includes('Generate the .vth theme file') ||
- allText.includes('Generate complete VL .vth source code') ||
- allText.includes('Generate complete VL .vth theme file')
- ) {
- return { content: fixture.vthCode, model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate the .vs service domain file for:') || allText.includes('Generate complete VL .vs source code')) {
- return { content: serviceCode(target?.domainId || target?.id || 'UnknownService'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('DomainItem:')) {
- return { content: serviceCode(target?.domainId || target?.id || 'UnknownService'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate the .cp component file for:') || allText.includes('Generate complete VL .cp source code')) {
- return { content: componentCode(target?.id || 'UnknownComponent'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('ComponentItem:')) {
- return { content: componentCode(target?.id || 'UnknownComponent'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate complete VL .sc source code') || allText.includes('Generate the .sc section file for:')) {
- return { content: sectionCode(target?.id || 'UnknownSection'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('SectionItem:')) {
- return { content: sectionCode(target?.id || 'UnknownSection'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('Generate complete VL .vx source code') || allText.includes('Generate the .vx app file for:')) {
- return { content: appCode(target?.appId || target?.id || 'UnknownApp'), model: 'fake-parallel-smoke', usage: {} };
- }
- if (allText.includes('AppItem:')) {
- return { content: appCode(target?.appId || target?.id || 'UnknownApp'), model: 'fake-parallel-smoke', usage: {} };
- }
- throw new Error(`Unhandled fake LLM prompt: ${allText.slice(0, 200)}`);
- } finally {
- probe.active--;
- }
- },
- };
- }
- async function collectFiles(dir, prefix = '') {
- const entries = await fs.readdir(path.join(dir, prefix), { withFileTypes: true }).catch(() => []);
- const files = [];
- for (const entry of entries) {
- const rel = path.join(prefix, entry.name);
- if (entry.isDirectory()) {
- files.push(...await collectFiles(dir, rel));
- } else {
- files.push(rel);
- }
- }
- return files.sort();
- }
- async function runExistingWorkflow(file, options = {}) {
- const workflowPath = path.join(process.cwd(), '.vl-code', 'workflows', file);
- const workflow = JSON.parse(await fs.readFile(workflowPath, 'utf8'));
- const workDir = path.join('/tmp/vlcode-lite-parallel-workflow-test', `${file.replace(/\.json$/, '')}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
- const fixture = createFixture(options.projectName);
- const probe = options.sharedProbe || { active: 0, maxActive: 0 };
- const errors = [];
- const done = [];
- await fs.rm(workDir, { recursive: true, force: true });
- await fs.mkdir(workDir, { recursive: true });
- const executor = new WorkflowExecutor({ workDir, model: 'fake-parallel-smoke' });
- executor._resolveDocCenterDocs = async () => {};
- executor._buildLLMAdapter = function buildLLMAdapter() {
- return createFakeLLM(fixture, probe, options);
- };
- await executor.execute(workflow, {
- userRequest: 'Generate a small inventory app',
- userRequirement: 'Generate a small inventory app',
- targetLang: 'zh-CN',
- }, {
- onNodeDone: (evt) => done.push(evt.nodeId),
- onNodeError: (evt) => errors.push(evt.nodeId),
- });
- return {
- workDir,
- files: await collectFiles(workDir),
- errors,
- done,
- maxActive: probe.maxActive,
- };
- }
- const concurrentWorkflow = {
- version: '3.16',
- name: 'ConcurrentWorkflowSmoke',
- steps: [
- {
- id: 'LLM_First',
- in: {
- stream: true,
- messages: [{ role: 'user', content: 'Generate the .vdb database schema file based on the databases defined in ProjectMeta.' }],
- },
- out: { '$first': '=_result' },
- next: 'LLM_Second',
- },
- {
- id: 'LLM_Second',
- in: {
- stream: true,
- messages: [{ role: 'user', content: 'Generate the .vdb database schema file based on the databases defined in ProjectMeta.' }],
- },
- out: { '$second': '=_result' },
- next: 'Stop_End',
- },
- { id: 'Stop_End' },
- ],
- };
- async function runConcurrentInstance(workDir, sharedProbe) {
- const fixture = createFixture('ConcurrentApp');
- const executor = new WorkflowExecutor({ workDir, model: 'fake-parallel-smoke' });
- executor._resolveDocCenterDocs = async () => {};
- executor._buildLLMAdapter = function buildLLMAdapter() {
- return createFakeLLM(fixture, sharedProbe, { delayMs: 120 });
- };
- await fs.rm(workDir, { recursive: true, force: true });
- await fs.mkdir(workDir, { recursive: true });
- await executor.execute(concurrentWorkflow, {}, {});
- }
- console.log('\n── Parallel Workflow Codegen Smoke ──');
- await test('parallel-codegen has real internal concurrency and writes all code files', async () => {
- const result = await runExistingWorkflow('parallel-codegen.json');
- assert(result.maxActive >= 4, `Expected internal concurrency >=4, got ${result.maxActive}`);
- assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
- for (const rel of [
- '.vl-code/ProjectMeta.json',
- 'Process/ProjectMeta.json',
- 'Database/SmokeApp.vdb',
- 'Theme/Theme.vth',
- 'Services/ItemService.vs',
- 'Services/OrderService.vs',
- 'ExtComponents/ItemCard.cp',
- 'ExtComponents/OrderBadge.cp',
- 'Sections/HomePage.sc',
- 'Sections/OrdersPage.sc',
- 'Apps/MainApp.vx',
- 'Apps/AdminPortalShell.vx',
- ]) {
- assertIncludes(result.files, rel);
- }
- });
- await test('meta-direct-codegen keeps DB and Theme fanout working', async () => {
- const result = await runExistingWorkflow('meta-direct-codegen.json');
- assert(result.maxActive >= 2, `Expected concurrency >=2, got ${result.maxActive}`);
- assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
- for (const rel of [
- '.vl-code/ProjectMeta.json',
- 'Database/SmokeApp.vdb',
- 'Theme/Theme.vth',
- 'Services/ItemService.vs',
- 'Services/OrderService.vs',
- 'ExtComponents/ItemCard.cp',
- 'ExtComponents/OrderBadge.cp',
- 'Sections/HomePage.sc',
- 'Sections/OrdersPage.sc',
- 'Apps/MainApp.vx',
- 'Apps/AdminPortalShell.vx',
- ]) {
- assertIncludes(result.files, rel);
- }
- });
- await test('3-file-codegen generates auxiliary specs and target VL files', async () => {
- const result = await runExistingWorkflow('3-file-codegen.json');
- assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
- for (const rel of [
- 'Process/Artifacts/PRD.json',
- 'Process/Artifacts/ServiceMap.json',
- 'Process/Artifacts/UIMap.json',
- 'Database/SmokeApp.vdb',
- 'Theme/Theme.vth',
- 'Services/ItemService.vs',
- 'Services/OrderService.vs',
- 'ExtComponents/ItemCard.cp',
- 'ExtComponents/OrderBadge.cp',
- 'Sections/HomePage.sc',
- 'Sections/OrdersPage.sc',
- 'Apps/MainApp.vx',
- 'Apps/AdminPortalShell.vx',
- ]) {
- assertIncludes(result.files, rel);
- }
- });
- await test('6-file-codegen runs parallel spec fanout and parallel code generation', async () => {
- const result = await runExistingWorkflow('6-file-codegen.json');
- assert(result.maxActive >= 4, `Expected concurrency >=4, got ${result.maxActive}`);
- assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
- for (const rel of [
- 'Process/Specs/Blueprint.json',
- 'Process/Specs/DataSchema.json',
- 'Process/Specs/LayoutPlan.json',
- 'Process/Specs/FileManifest.json',
- 'Process/Specs/ContractSheet.json',
- 'Process/Specs/StateBehavior.json',
- 'Database/SmokeApp.vdb',
- 'Theme/Theme.vth',
- 'Services/ItemService.vs',
- 'Services/OrderService.vs',
- 'ExtComponents/ItemCard.cp',
- 'ExtComponents/OrderBadge.cp',
- 'Sections/HomePage.sc',
- 'Sections/OrdersPage.sc',
- 'Apps/MainApp.vx',
- 'Apps/AdminPortalShell.vx',
- ]) {
- assertIncludes(result.files, rel);
- }
- });
- await test('9-file-codegen runs full parallel generation fanout', async () => {
- const result = await runExistingWorkflow('9-file-codegen.json');
- assert(result.maxActive >= 2, `Expected concurrency >=2, got ${result.maxActive}`);
- assert(result.errors.length === 0, `Unexpected errors: ${result.errors.join(', ')}`);
- for (const rel of [
- 'Process/Specs/IntentSpec.json',
- 'Process/Specs/DomainSchema.json',
- 'Process/Specs/ServiceContractMap.json',
- 'Process/Specs/QualityGateSpec.json',
- 'Process/Specs/UIContractMap.json',
- 'Process/Specs/ModuleManifest.json',
- 'Process/Specs/DependencyGraph.json',
- 'Process/Run/ExecutionPlan.json',
- 'Database/SmokeApp.vdb',
- 'Theme/Theme.vth',
- 'Services/ItemService.vs',
- 'Services/OrderService.vs',
- 'ExtComponents/ItemCard.cp',
- 'ExtComponents/OrderBadge.cp',
- 'Sections/HomePage.sc',
- 'Sections/OrdersPage.sc',
- 'Apps/MainApp.vx',
- 'Apps/AdminPortalShell.vx',
- ]) {
- assertIncludes(result.files, rel);
- }
- });
- await test('parallel component branch failure does not abort sibling code generation', async () => {
- const result = await runExistingWorkflow('parallel-codegen.json', { failId: 'OrderBadge' });
- assert(result.errors.length >= 1, 'Expected one skipped branch error');
- assertIncludes(result.files, 'ExtComponents/ItemCard.cp');
- assert(!result.files.includes('ExtComponents/OrderBadge.cp'), 'Failed branch file should be absent');
- assertIncludes(result.files, 'Services/ItemService.vs');
- assertIncludes(result.files, 'Apps/MainApp.vx');
- });
- await test('two executor instances can execute concurrently without state bleed', async () => {
- const sharedProbe = { active: 0, maxActive: 0 };
- const dirA = '/tmp/vlcode-lite-parallel-workflow-test/concurrent-a';
- const dirB = '/tmp/vlcode-lite-parallel-workflow-test/concurrent-b';
- await Promise.all([
- runConcurrentInstance(dirA, sharedProbe),
- runConcurrentInstance(dirB, sharedProbe),
- ]);
- assert(sharedProbe.maxActive >= 2, `Expected cross-instance concurrency >=2, got ${sharedProbe.maxActive}`);
- });
- console.log(`\n── Results ──\n\n ${passed} passed, ${failed} failed\n`);
- process.exit(failed > 0 ? 1 : 0);
|