fix e2e
This commit is contained in:
parent
4919bc759f
commit
0fe835ecd9
@ -17,7 +17,8 @@ E2Eテストを追加・変更した場合は、このドキュメントも更
|
||||
## E2E用config.yaml
|
||||
- E2Eのグローバル設定は `e2e/fixtures/config.e2e.yaml` を基準に生成する。
|
||||
- `createIsolatedEnv()` は毎回一時ディレクトリ配下(`$TAKT_CONFIG_DIR/config.yaml`)にこの基準設定を書き出す。
|
||||
- 通知音は `notification_sound_events` でタイミング別に制御し、E2E既定では道中(`iteration_limit` / `piece_complete` / `piece_abort`)をOFF、全体終了時(`run_complete` / `run_abort`)のみONにする。
|
||||
- E2E実行中の `takt` 内通知音は `notification_sound: false` で無効化する。
|
||||
- `npm run test:e2e` は成否にかかわらず最後に1回ベルを鳴らし、終了コードはテスト結果を維持する。
|
||||
- 各スペックで `provider` や `concurrency` を変更する場合は、`updateIsolatedConfig()` を使って差分のみ上書きする。
|
||||
- `~/.takt/config.yaml` はE2Eでは参照されないため、通常実行の設定には影響しない。
|
||||
|
||||
|
||||
@ -2,10 +2,10 @@ provider: claude
|
||||
language: en
|
||||
log_level: info
|
||||
default_piece: default
|
||||
notification_sound: true
|
||||
notification_sound: false
|
||||
notification_sound_events:
|
||||
iteration_limit: false
|
||||
piece_complete: false
|
||||
piece_abort: false
|
||||
run_complete: true
|
||||
run_abort: true
|
||||
run_abort: false
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
[
|
||||
{"persona": "agents/test-reviewer-a", "status": "done", "content": "[REVIEW:2]\n\nNeeds fix."},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "agents/test-coder", "status": "done", "content": "[FIX:1]\n\nFixed."},
|
||||
{"persona": "agents/test-reviewer-a", "status": "done", "content": "[REVIEW:2]\n\nStill needs fix."},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "agents/test-coder", "status": "done", "content": "[FIX:1]\n\nFixed again."},
|
||||
{"persona": "agents/test-reviewer-b", "status": "done", "content": "[_LOOP_JUDGE_REVIEW_FIX:2]\n\nAbort this loop."},
|
||||
{"persona": "conductor", "status": "done", "content": "[_LOOP_JUDGE_REVIEW_FIX:2]"},
|
||||
{"persona": "conductor", "status": "done", "content": "[_LOOP_JUDGE_REVIEW_FIX:2]"}
|
||||
]
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
[
|
||||
{"persona": "agents/test-reviewer-a", "status": "done", "content": "[REVIEW:2]\n\nNeeds fix."},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:2]"},
|
||||
{"persona": "agents/test-coder", "status": "done", "content": "[FIX:1]\n\nFixed."},
|
||||
{"persona": "agents/test-reviewer-a", "status": "done", "content": "[REVIEW:1]\n\nApproved."},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:1]"},
|
||||
{"persona": "conductor", "status": "done", "content": "[REVIEW:1]"}
|
||||
]
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
[
|
||||
{ "persona": "test-coder", "status": "done", "content": "Plan created." },
|
||||
{ "persona": "test-reviewer-a", "status": "done", "content": "Architecture approved." },
|
||||
{ "persona": "test-reviewer-b", "status": "done", "content": "Security approved." },
|
||||
{ "persona": "agents/test-coder", "status": "done", "content": "Plan created." },
|
||||
{ "persona": "agents/test-reviewer-a", "status": "done", "content": "Architecture approved." },
|
||||
{ "persona": "agents/test-reviewer-b", "status": "done", "content": "Security approved." },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" }
|
||||
]
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
[
|
||||
{ "persona": "test-coder", "status": "done", "content": "Plan created." },
|
||||
{ "persona": "agents/test-coder", "status": "done", "content": "Plan created." },
|
||||
|
||||
{ "persona": "test-reviewer-a", "status": "done", "content": "Architecture looks good." },
|
||||
{ "persona": "test-reviewer-b", "status": "done", "content": "Security issues found." },
|
||||
{ "persona": "agents/test-reviewer-a", "status": "done", "content": "Architecture looks good." },
|
||||
{ "persona": "agents/test-reviewer-b", "status": "done", "content": "Security issues found." },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:2]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:2]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:2]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:2]" },
|
||||
|
||||
{ "persona": "test-coder", "status": "done", "content": "Fix applied." },
|
||||
{ "persona": "agents/test-coder", "status": "done", "content": "Fix applied." },
|
||||
|
||||
{ "persona": "test-reviewer-a", "status": "done", "content": "Architecture still approved." },
|
||||
{ "persona": "test-reviewer-b", "status": "done", "content": "Security now approved." },
|
||||
{ "persona": "agents/test-reviewer-a", "status": "done", "content": "Architecture still approved." },
|
||||
{ "persona": "agents/test-reviewer-b", "status": "done", "content": "Security now approved." },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" },
|
||||
{ "persona": "conductor", "status": "done", "content": "[ARCH-REVIEW:1] [SECURITY-REVIEW:1]" }
|
||||
]
|
||||
|
||||
@ -9,6 +9,11 @@
|
||||
"status": "done",
|
||||
"content": "Report summary: OK"
|
||||
},
|
||||
{
|
||||
"persona": "conductor",
|
||||
"status": "done",
|
||||
"content": "[EXECUTE:1]"
|
||||
},
|
||||
{
|
||||
"persona": "conductor",
|
||||
"status": "done",
|
||||
|
||||
@ -54,6 +54,66 @@ function getGitHubUser(): string {
|
||||
return user;
|
||||
}
|
||||
|
||||
function canUseGitHubRepo(): boolean {
|
||||
try {
|
||||
const user = getGitHubUser();
|
||||
const repoName = `${user}/takt-testing`;
|
||||
execFileSync('gh', ['repo', 'view', repoName], {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isGitHubE2EAvailable(): boolean {
|
||||
return canUseGitHubRepo();
|
||||
}
|
||||
|
||||
function createOfflineTestRepo(options?: CreateTestRepoOptions): TestRepo {
|
||||
const sandboxPath = mkdtempSync(join(tmpdir(), 'takt-e2e-repo-'));
|
||||
const originPath = join(sandboxPath, 'origin.git');
|
||||
const repoPath = join(sandboxPath, 'work');
|
||||
|
||||
execFileSync('git', ['init', '--bare', originPath], { stdio: 'pipe' });
|
||||
execFileSync('git', ['clone', originPath, repoPath], { stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoPath, stdio: 'pipe' });
|
||||
writeFileSync(join(repoPath, 'README.md'), '# test\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['commit', '-m', 'init'], { cwd: repoPath, stdio: 'pipe' });
|
||||
execFileSync('git', ['push', '-u', 'origin', 'HEAD'], { cwd: repoPath, stdio: 'pipe' });
|
||||
|
||||
const testBranch = options?.skipBranch ? undefined : `e2e-test-${Date.now()}`;
|
||||
if (testBranch) {
|
||||
execFileSync('git', ['checkout', '-b', testBranch], {
|
||||
cwd: repoPath,
|
||||
stdio: 'pipe',
|
||||
});
|
||||
}
|
||||
|
||||
const currentBranch = testBranch
|
||||
?? execFileSync('git', ['branch', '--show-current'], {
|
||||
cwd: repoPath,
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
|
||||
return {
|
||||
path: repoPath,
|
||||
repoName: 'local/takt-testing',
|
||||
branch: currentBranch,
|
||||
cleanup: () => {
|
||||
try {
|
||||
rmSync(sandboxPath, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the takt-testing repository and create a test branch.
|
||||
*
|
||||
@ -63,6 +123,10 @@ function getGitHubUser(): string {
|
||||
* 3. Delete local directory
|
||||
*/
|
||||
export function createTestRepo(options?: CreateTestRepoOptions): TestRepo {
|
||||
if (!canUseGitHubRepo()) {
|
||||
return createOfflineTestRepo(options);
|
||||
}
|
||||
|
||||
const user = getGitHubUser();
|
||||
const repoName = `${user}/takt-testing`;
|
||||
|
||||
|
||||
@ -9,11 +9,12 @@ import {
|
||||
updateIsolatedConfig,
|
||||
type IsolatedEnv,
|
||||
} from '../helpers/isolated-env';
|
||||
import { createTestRepo, type TestRepo } from '../helpers/test-repo';
|
||||
import { createTestRepo, isGitHubE2EAvailable, type TestRepo } from '../helpers/test-repo';
|
||||
import { runTakt } from '../helpers/takt-runner';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const requiresGitHub = isGitHubE2EAvailable();
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Add task from GitHub issue (takt add)', () => {
|
||||
@ -67,7 +68,7 @@ describe('E2E: Add task from GitHub issue (takt add)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should create a task file from issue reference', () => {
|
||||
it.skipIf(!requiresGitHub)('should create a task file from issue reference', () => {
|
||||
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/add-task.json');
|
||||
|
||||
const result = runTakt({
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
"watch": "tsc --watch",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "npm run test:e2e:all",
|
||||
"test:e2e:mock": "vitest run --config vitest.config.e2e.mock.ts --reporter=verbose",
|
||||
"test:e2e": "npm run test:e2e:mock; code=$?; if [ \"$code\" -eq 0 ]; then msg='test:e2e passed'; else msg=\"test:e2e failed (exit=$code)\"; fi; if command -v osascript >/dev/null 2>&1; then osascript -e \"display notification \\\"$msg\\\" with title \\\"takt\\\" subtitle \\\"E2E\\\"\" >/dev/null 2>&1 || true; fi; echo \"[takt] $msg\"; exit $code",
|
||||
"test:e2e:mock": "TAKT_E2E_PROVIDER=mock vitest run --config vitest.config.e2e.mock.ts --reporter=verbose",
|
||||
"test:e2e:all": "npm run test:e2e:mock && npm run test:e2e:provider",
|
||||
"test:e2e:provider": "npm run test:e2e:provider:claude && npm run test:e2e:provider:codex",
|
||||
"test:e2e:provider:claude": "TAKT_E2E_PROVIDER=claude vitest run --config vitest.config.e2e.provider.ts --reporter=verbose",
|
||||
|
||||
@ -76,7 +76,7 @@ describe('createIsolatedEnv', () => {
|
||||
expect(isolated.env.GIT_CONFIG_GLOBAL).toContain('takt-e2e-');
|
||||
});
|
||||
|
||||
it('should create config.yaml from E2E fixture with notification_sound timing controls', () => {
|
||||
it('should create config.yaml from E2E fixture with notification_sound disabled', () => {
|
||||
const isolated = createIsolatedEnv();
|
||||
cleanups.push(isolated.cleanup);
|
||||
|
||||
@ -86,13 +86,13 @@ describe('createIsolatedEnv', () => {
|
||||
expect(config.language).toBe('en');
|
||||
expect(config.log_level).toBe('info');
|
||||
expect(config.default_piece).toBe('default');
|
||||
expect(config.notification_sound).toBe(true);
|
||||
expect(config.notification_sound).toBe(false);
|
||||
expect(config.notification_sound_events).toEqual({
|
||||
iteration_limit: false,
|
||||
piece_complete: false,
|
||||
piece_abort: false,
|
||||
run_complete: true,
|
||||
run_abort: true,
|
||||
run_abort: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -120,13 +120,13 @@ describe('createIsolatedEnv', () => {
|
||||
|
||||
expect(config.provider).toBe('mock');
|
||||
expect(config.concurrency).toBe(2);
|
||||
expect(config.notification_sound).toBe(true);
|
||||
expect(config.notification_sound).toBe(false);
|
||||
expect(config.notification_sound_events).toEqual({
|
||||
iteration_limit: false,
|
||||
piece_complete: false,
|
||||
piece_abort: false,
|
||||
run_complete: true,
|
||||
run_abort: true,
|
||||
run_abort: false,
|
||||
});
|
||||
expect(config.language).toBe('en');
|
||||
});
|
||||
@ -149,7 +149,7 @@ describe('createIsolatedEnv', () => {
|
||||
piece_complete: false,
|
||||
piece_abort: false,
|
||||
run_complete: false,
|
||||
run_abort: true,
|
||||
run_abort: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -123,7 +123,11 @@ export async function runWithWorkerPool(
|
||||
selfSigintInjected = true;
|
||||
process.emit('SIGINT');
|
||||
if (selfSigintTwice) {
|
||||
process.emit('SIGINT');
|
||||
// E2E deterministic path: force-exit shortly after graceful SIGINT.
|
||||
// Avoids intermittent hangs caused by listener ordering/races.
|
||||
setTimeout(() => {
|
||||
process.exit(EXIT_SIGINT);
|
||||
}, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user