Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 75 additions & 14 deletions src/daemon/handlers/__tests__/session-test-discovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,43 @@ test('discoverReplayTestEntries includes Maestro yaml flows for Maestro test sui
});

assert.deepEqual(
entries.map((entry) => path.basename(entry.path)),
['01-flow.yaml', '02-flow.yml', '03-flow.ad'],
new Set(entries.map((entry) => path.basename(entry.path))),
new Set(['01-flow.yaml', '02-flow.yml', '03-flow.ad']),
);
assert.deepEqual(
entries.map((entry) => entry.kind),
['run', 'run', 'run'],
);
assert.equal(entries[0]?.kind, 'run');
if (entries[0]?.kind === 'run') {
assert.equal(entries[0].title, 'Bottom Tabs - Dynamic');
const namedFlow = entries.find((entry) => path.basename(entry.path) === '01-flow.yaml');
assert.equal(namedFlow?.kind, 'run');
if (namedFlow?.kind === 'run') {
assert.equal(namedFlow.title, 'Bottom Tabs - Dynamic');
}
});

test('discoverReplayTestEntries sorts Maestro directory flows by extension group then path', () => {
test('discoverReplayTestEntries preserves Maestro directory filesystem order', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-discovery-maestro-sort-'));
const flowFiles = ['10-legacy.ad', '30-zeta.yaml', '05-compat.ad', '20-beta.yml'];
for (const fileName of flowFiles) {
const body = fileName.endsWith('.ad') ? 'open "Demo"\n' : 'appId: demo\n---\n- launchApp\n';
fs.writeFileSync(path.join(root, fileName), body);
}

const globSync = vi.spyOn(fs, 'globSync').mockImplementation((pattern, options) => {
assert.equal((options as { cwd?: string } | undefined)?.cwd, root);
if (pattern === '**/*.yaml') return ['30-zeta.yaml'];
if (pattern === '**/*.yml') return ['20-beta.yml'];
if (pattern === '**/*.ad') return ['10-legacy.ad', '05-compat.ad'];
return [];
const opendirSync = vi.spyOn(fs, 'opendirSync').mockImplementation((directory) => {
assert.equal(directory, root);
let index = 0;
return {
readSync: () => {
const name = flowFiles[index++];
if (!name) return null;
return {
name,
isDirectory: () => false,
isFile: () => true,
} as fs.Dirent;
},
closeSync: () => {},
} as fs.Dir;
});

try {
Expand All @@ -110,10 +120,61 @@ test('discoverReplayTestEntries sorts Maestro directory flows by extension group

assert.deepEqual(
entries.map((entry) => path.basename(entry.path)),
['20-beta.yml', '30-zeta.yaml', '05-compat.ad', '10-legacy.ad'],
['10-legacy.ad', '30-zeta.yaml', '05-compat.ad', '20-beta.yml'],
);
} finally {
opendirSync.mockRestore();
}
});

test('discoverReplayTestEntries preserves Maestro nested directory DFS order', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-discovery-maestro-dfs-'));
const nested = path.join(root, 'nested');
fs.mkdirSync(nested, { recursive: true });
fs.writeFileSync(path.join(root, '30-root-a.yaml'), 'appId: demo\n---\n- launchApp\n');
fs.writeFileSync(path.join(nested, '10-child.yml'), 'appId: demo\n---\n- launchApp\n');
fs.writeFileSync(path.join(root, '20-root-c.ad'), 'open "Demo"\n');

type MockDirEntry = { name: string; directory: boolean };
const opendirSync = vi.spyOn(fs, 'opendirSync').mockImplementation((directory) => {
let entries: MockDirEntry[] = [];
if (directory === root) {
entries = [
{ name: '30-root-a.yaml', directory: false },
{ name: 'nested', directory: true },
{ name: '20-root-c.ad', directory: false },
];
} else if (directory === nested) {
entries = [{ name: '10-child.yml', directory: false }];
}
let index = 0;
return {
readSync: () => {
const entry = entries[index++];
if (!entry) return null;
return {
name: entry.name,
isDirectory: () => entry.directory,
isFile: () => !entry.directory,
} as fs.Dirent;
},
closeSync: () => {},
} as fs.Dir;
});

try {
const entries = discoverReplayTestEntries({
inputs: [root],
cwd: root,
replayBackend: 'maestro',
});

assert.deepEqual(
entries.map((entry) => path.relative(root, entry.path)),
['30-root-a.yaml', path.join('nested', '10-child.yml'), '20-root-c.ad'],
);
} finally {
globSync.mockRestore();
opendirSync.mockRestore();
}
});

Expand Down
49 changes: 47 additions & 2 deletions src/daemon/handlers/session-test-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,62 @@ function discoverReplayTestFilePaths(
const files: string[] = [];
const expandedGroups: string[][] = [];
for (const input of inputs) {
const expanded = expandReplayTestInput(input, cwd, extensions);
const expanded = expandMaestroReplayTestInput(input, cwd, extensions);
if (expanded.source === 'file') {
files.push(...expanded.paths);
} else {
expandedGroups.push(sortMaestroExpandedReplayTestPaths(expanded.paths));
expandedGroups.push(
expanded.source === 'directory'
? uniqueNormalizedPaths(expanded.paths)
: sortMaestroExpandedReplayTestPaths(expanded.paths),
);
}
}

return uniqueNormalizedPaths([...files, ...expandedGroups.flat()]);
}

function expandMaestroReplayTestInput(
input: string,
cwd: string,
extensions: Set<string>,
): { paths: string[]; source: ReplayTestInputSource } {
const expandedInput = SessionStore.expandHome(input, cwd);
if (fs.existsSync(expandedInput) && fs.statSync(expandedInput).isDirectory()) {
return {
paths: readMaestroDirectoryReplayTestPaths(expandedInput, extensions),
source: 'directory',
};
}

return expandReplayTestInput(input, cwd, extensions);
}

function readMaestroDirectoryReplayTestPaths(
directoryPath: string,
extensions: Set<string>,
): string[] {
const paths: string[] = [];
// Maestro's Java Files.walk follows native directory iteration order. Keep
// this unsorted so folder suites, and sharding derived from them, match
// Maestro on the same machine even though order can differ across hosts.
const directory = fs.opendirSync(directoryPath);
try {
let entry: fs.Dirent | null;
while ((entry = directory.readSync()) !== null) {
const filePath = path.join(directoryPath, entry.name);
if (entry.isDirectory()) {
paths.push(...readMaestroDirectoryReplayTestPaths(filePath, extensions));
} else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
paths.push(filePath);
}
}
} finally {
directory.closeSync();
}
return paths;
}

function expandReplayTestInput(
input: string,
cwd: string,
Expand Down
Loading