mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
fix: clarify clean-xcuitest output (#1018)
* fix: clarify clean-xcuitest output * fix: simplify clean-xcuitest formatter * fix: clarify clean-xcuitest failure output
This commit is contained in:
committed by
GitHub
parent
9e14f0e8eb
commit
fcb7e32f1c
@@ -25,6 +25,13 @@ const DERIVED_PATHS = new Map([
|
||||
['tvos', path.join(DERIVED_ROOT, 'tvos')],
|
||||
['visionos', path.join(DERIVED_ROOT, 'visionos')],
|
||||
]);
|
||||
const PLATFORM_LABELS = new Map([
|
||||
['ios', 'iOS'],
|
||||
['macos', 'macOS'],
|
||||
['tvos', 'tvOS'],
|
||||
['visionos', 'visionOS'],
|
||||
]);
|
||||
const MAX_SUMMARY_ENTRY_NAMES = 3;
|
||||
|
||||
for (const platform of requested) {
|
||||
if (!supported.has(platform)) {
|
||||
@@ -34,23 +41,92 @@ for (const platform of requested) {
|
||||
continue;
|
||||
}
|
||||
const targetPath = resolveDerivedPath(platform);
|
||||
cleanDerivedPath(platform, targetPath);
|
||||
console.log(`Removed ${targetPath}`);
|
||||
try {
|
||||
const result = cleanDerivedPath(platform, targetPath);
|
||||
console.log(formatCleanupResult(platform, targetPath, result));
|
||||
} catch (error) {
|
||||
console.error(formatCleanupError(platform, targetPath, error));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
function cleanDerivedPath(platform, targetPath) {
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
return { status: 'skipped', reason: 'not-found' };
|
||||
}
|
||||
if (platform !== 'ios') {
|
||||
fs.rmSync(targetPath, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
return;
|
||||
return { status: 'removed' };
|
||||
}
|
||||
const removedEntries = [];
|
||||
const preservedEntries = [];
|
||||
for (const entry of fs.readdirSync(targetPath, { withFileTypes: true })) {
|
||||
if (!ROOT_TRANSIENT_ENTRY_NAMES.has(entry.name)) continue;
|
||||
if (!ROOT_TRANSIENT_ENTRY_NAMES.has(entry.name)) {
|
||||
preservedEntries.push(entry.name);
|
||||
continue;
|
||||
}
|
||||
fs.rmSync(path.join(targetPath, entry.name), { recursive: true, force: true });
|
||||
removedEntries.push(entry.name);
|
||||
}
|
||||
if (removedEntries.length === 0) {
|
||||
return {
|
||||
status: 'skipped',
|
||||
reason: 'no-transient-entries',
|
||||
preservedEntries,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: 'removed',
|
||||
removedEntries,
|
||||
preservedEntries,
|
||||
};
|
||||
}
|
||||
|
||||
function formatCleanupResult(platform, targetPath, result) {
|
||||
const platformLabel = resolvePlatformLabel(platform);
|
||||
if (platform !== 'ios') return formatWholeDerivedCleanupResult(platformLabel, targetPath, result);
|
||||
return formatIosCleanupResult(platformLabel, targetPath, result);
|
||||
}
|
||||
|
||||
function formatWholeDerivedCleanupResult(platformLabel, targetPath, result) {
|
||||
return result.status === 'removed'
|
||||
? `Removed ${platformLabel} XCTest derived data: ${targetPath}`
|
||||
: `Skipped ${platformLabel} XCTest cleanup: ${targetPath} not found`;
|
||||
}
|
||||
|
||||
function formatIosCleanupResult(platformLabel, targetPath, result) {
|
||||
if (result.status === 'skipped' && result.reason === 'not-found') {
|
||||
return `Skipped ${platformLabel} XCTest cleanup: ${targetPath} not found`;
|
||||
}
|
||||
const keptSuffix = formatKeptEntriesSuffix(result.preservedEntries);
|
||||
if (result.status === 'skipped') {
|
||||
return `Skipped ${platformLabel} XCTest cleanup under ${targetPath}: no transient entries found${keptSuffix}`;
|
||||
}
|
||||
return `Removed ${platformLabel} XCTest transient entries under ${targetPath}: ${summarizeEntryNames(result.removedEntries)}${keptSuffix}`;
|
||||
}
|
||||
|
||||
function formatKeptEntriesSuffix(preservedEntries) {
|
||||
return preservedEntries.length > 0 ? `; kept ${summarizeEntryNames(preservedEntries)}` : '';
|
||||
}
|
||||
|
||||
function formatCleanupError(platform, targetPath, error) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
return `Failed to clean ${resolvePlatformLabel(platform)} XCTest derived data under ${targetPath}: ${detail}`;
|
||||
}
|
||||
|
||||
function resolvePlatformLabel(platform) {
|
||||
const platformLabel = PLATFORM_LABELS.get(platform);
|
||||
if (platformLabel) return platformLabel;
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
function summarizeEntryNames(entryNames) {
|
||||
const names = [...entryNames].sort();
|
||||
if (names.length <= MAX_SUMMARY_ENTRY_NAMES) {
|
||||
return names.join(', ');
|
||||
}
|
||||
return `${names.slice(0, MAX_SUMMARY_ENTRY_NAMES).join(', ')} (+${names.length - MAX_SUMMARY_ENTRY_NAMES} more)`;
|
||||
}
|
||||
|
||||
function resolveDerivedPath(platform) {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { runCmdSync } from '../../src/utils/exec.ts';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
const scriptPath = path.join(repoRoot, 'scripts', 'clean-xcuitest-derived.mjs');
|
||||
|
||||
test('clean-xcuitest ios removes only transient root entries and reports preserved cache entries', () => {
|
||||
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-ios-'));
|
||||
try {
|
||||
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
||||
fs.mkdirSync(path.join(derivedRoot, 'Build'), { recursive: true });
|
||||
fs.mkdirSync(path.join(derivedRoot, 'Logs'), { recursive: true });
|
||||
fs.mkdirSync(path.join(derivedRoot, 'cache-warm-runner'), { recursive: true });
|
||||
fs.mkdirSync(path.join(derivedRoot, 'macos'), { recursive: true });
|
||||
|
||||
const result = runCleanXcuitest(homeDir, 'ios');
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
assert.equal(
|
||||
result.stdout.trim(),
|
||||
`Removed iOS XCTest transient entries under ${derivedRoot}: Build, Logs; kept cache-warm-runner, macos`,
|
||||
);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'Build')), false);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'Logs')), false);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'cache-warm-runner')), true);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'macos')), true);
|
||||
} finally {
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('clean-xcuitest ios reports a no-op when only preserved entries remain', () => {
|
||||
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-ios-noop-'));
|
||||
try {
|
||||
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
||||
fs.mkdirSync(path.join(derivedRoot, 'cache-warm-runner'), { recursive: true });
|
||||
fs.mkdirSync(path.join(derivedRoot, 'tvos'), { recursive: true });
|
||||
|
||||
const result = runCleanXcuitest(homeDir, 'ios');
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
assert.equal(
|
||||
result.stdout.trim(),
|
||||
`Skipped iOS XCTest cleanup under ${derivedRoot}: no transient entries found; kept cache-warm-runner, tvos`,
|
||||
);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'cache-warm-runner')), true);
|
||||
assert.equal(fs.existsSync(path.join(derivedRoot, 'tvos')), true);
|
||||
} finally {
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('clean-xcuitest macos skips a missing derived directory', () => {
|
||||
const homeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'agent-device-clean-xcuitest-macos-missing-'),
|
||||
);
|
||||
try {
|
||||
const derivedPath = path.join(homeDir, '.agent-device', 'apple-runner', 'derived', 'macos');
|
||||
|
||||
const result = runCleanXcuitest(homeDir, 'macos');
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
assert.equal(result.stdout.trim(), `Skipped macOS XCTest cleanup: ${derivedPath} not found`);
|
||||
} finally {
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('clean-xcuitest macos removes the entire platform directory when present', () => {
|
||||
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-macos-'));
|
||||
try {
|
||||
const derivedPath = path.join(homeDir, '.agent-device', 'apple-runner', 'derived', 'macos');
|
||||
fs.mkdirSync(derivedPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(derivedPath, 'marker.txt'), 'ok', 'utf8');
|
||||
|
||||
const result = runCleanXcuitest(homeDir, 'macos');
|
||||
assert.equal(result.exitCode, 0, result.stderr);
|
||||
assert.equal(result.stdout.trim(), `Removed macOS XCTest derived data: ${derivedPath}`);
|
||||
assert.equal(fs.existsSync(derivedPath), false);
|
||||
} finally {
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('clean-xcuitest reports cleanup failures directly', () => {
|
||||
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-clean-xcuitest-failure-'));
|
||||
const derivedRoot = path.join(homeDir, '.agent-device', 'apple-runner', 'derived');
|
||||
try {
|
||||
fs.mkdirSync(path.join(derivedRoot, 'Build'), { recursive: true });
|
||||
fs.chmodSync(derivedRoot, 0o500);
|
||||
|
||||
const result = runCleanXcuitest(homeDir, 'ios', { allowFailure: true });
|
||||
assert.equal(result.exitCode, 1);
|
||||
assert.equal(result.stdout, '');
|
||||
assert.match(
|
||||
result.stderr.trim(),
|
||||
new RegExp(`^Failed to clean iOS XCTest derived data under ${escapeRegExp(derivedRoot)}: `),
|
||||
);
|
||||
} finally {
|
||||
fs.chmodSync(derivedRoot, 0o700);
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function runCleanXcuitest(homeDir: string, ...args: Array<string | { allowFailure?: boolean }>) {
|
||||
const lastArg = args.at(-1);
|
||||
const options = typeof lastArg === 'object' ? lastArg : {};
|
||||
const platforms = (options === lastArg ? args.slice(0, -1) : args) as string[];
|
||||
return runCmdSync(process.execPath, [scriptPath, ...platforms], {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: homeDir,
|
||||
},
|
||||
allowFailure: options?.allowFailure,
|
||||
timeoutMs: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string) {
|
||||
return value.replaceAll(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
||||
}
|
||||
Reference in New Issue
Block a user