From 3203713fd54f9d0106b098e3290d7c9e23f6c2c7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 3 Apr 2026 12:28:50 -0700 Subject: [PATCH] Fix YouTube test yt-dlp fakes for non-Windows CI --- .../services/youtube/metadata-probe.test.ts | 14 ++- .../services/youtube/playback-resolve.test.ts | 8 +- .../services/youtube/track-download.test.ts | 100 +++++++++++++++++- src/core/services/youtube/track-probe.test.ts | 13 ++- 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/src/core/services/youtube/metadata-probe.test.ts b/src/core/services/youtube/metadata-probe.test.ts index 05e64c13..5ae196e0 100644 --- a/src/core/services/youtube/metadata-probe.test.ts +++ b/src/core/services/youtube/metadata-probe.test.ts @@ -16,8 +16,14 @@ async function withTempDir(fn: (dir: string) => Promise): Promise { function makeFakeYtDlpScript(dir: string, payload: string): void { const scriptPath = path.join(dir, 'yt-dlp'); - const script = `#!/usr/bin/env node + const script = process.platform === 'win32' + ? `#!/usr/bin/env node process.stdout.write(${JSON.stringify(payload)}); +` + : `#!/usr/bin/env sh +cat <<'EOF' | base64 -d +${Buffer.from(payload).toString('base64')} +EOF `; fs.writeFileSync(scriptPath, script, 'utf8'); if (process.platform !== 'win32') { @@ -28,8 +34,10 @@ process.stdout.write(${JSON.stringify(payload)}); function makeHangingFakeYtDlpScript(dir: string): void { const scriptPath = path.join(dir, 'yt-dlp'); - const script = `#!/usr/bin/env node -setInterval(() => {}, 1000); + const script = `#!/usr/bin/env sh +while :; do + sleep 1; +done `; fs.writeFileSync(scriptPath, script, 'utf8'); if (process.platform !== 'win32') { diff --git a/src/core/services/youtube/playback-resolve.test.ts b/src/core/services/youtube/playback-resolve.test.ts index c22616c2..1eee56d4 100644 --- a/src/core/services/youtube/playback-resolve.test.ts +++ b/src/core/services/youtube/playback-resolve.test.ts @@ -16,8 +16,14 @@ async function withTempDir(fn: (dir: string) => Promise): Promise { function makeFakeYtDlpScript(dir: string, payload: string): void { const scriptPath = path.join(dir, 'yt-dlp'); - const script = `#!/usr/bin/env node + const script = process.platform === 'win32' + ? `#!/usr/bin/env node process.stdout.write(${JSON.stringify(payload)}); +` + : `#!/usr/bin/env sh +cat <<'EOF' | base64 -d +${Buffer.from(payload).toString('base64')} +EOF `; fs.writeFileSync(scriptPath, script, 'utf8'); if (process.platform !== 'win32') { diff --git a/src/core/services/youtube/track-download.test.ts b/src/core/services/youtube/track-download.test.ts index 5a24bc1b..f340f714 100644 --- a/src/core/services/youtube/track-download.test.ts +++ b/src/core/services/youtube/track-download.test.ts @@ -93,6 +93,95 @@ process.exit(0); return scriptPath; } +function makeFakeYtDlpShellScript(dir: string): string { + const scriptPath = path.join(dir, 'yt-dlp'); + const script = `#!/usr/bin/env sh +has_auto_subs=0 +wants_auto_subs=0 +wants_manual_subs=0 +sub_lang='' +output_template='' + +while [ "$#" -gt 0 ]; do + case "$1" in + --write-auto-subs) + wants_auto_subs=1 + ;; + --write-subs) + wants_manual_subs=1 + ;; + --sub-langs) + sub_lang="$2" + shift + ;; + -o) + output_template="$2" + shift + ;; + esac + shift +done + +if [ "$YTDLP_EXPECT_AUTO_SUBS" = "1" ] && [ "$wants_auto_subs" != "1" ]; then + exit 2 +fi +if [ "$YTDLP_EXPECT_MANUAL_SUBS" = "1" ] && [ "$wants_manual_subs" != "1" ]; then + exit 3 +fi +if [ -n "$YTDLP_EXPECT_SUB_LANG" ] && [ "$sub_lang" != "$YTDLP_EXPECT_SUB_LANG" ]; then + exit 4 +fi + +prefix="$(printf '%s' "$output_template" | sed 's/%\.%(ext)s$//')" +if [ -z "$prefix" ]; then + exit 1 +fi +mkdir -p "$(dirname \"$prefix\")" + +if [ "$YTDLP_FAKE_MODE" = "multi" ]; then + OLD_IFS="$IFS" + IFS="," + for lang in $sub_lang; do + if [ -n "$lang" ]; then + printf 'WEBVTT\\n' > "${prefix}.${lang}.vtt" + fi + done + IFS="$OLD_IFS" +elif [ "$YTDLP_FAKE_MODE" = "rolling-auto" ]; then + cat <<'EOF' > "${prefix}.vtt" +WEBVTT + +00:00:01.000 --> 00:00:02.000 +今日は + +00:00:02.000 --> 00:00:03.000 +今日はいい天気ですね + +00:00:03.000 --> 00:00:04.000 +今日はいい天気ですね本当に + +EOF +elif [ "$YTDLP_FAKE_MODE" = "multi-primary-only-fail" ]; then + primary_lang="${sub_lang%%,*}" + if [ -n "$primary_lang" ]; then + printf 'WEBVTT\\n' > "${prefix}.${primary_lang}.vtt" + fi + printf "ERROR: Unable to download video subtitles for 'en': HTTP Error 429: Too Many Requests\\n" 1>&2 + exit 1 +elif [ "$YTDLP_FAKE_MODE" = "both" ]; then + printf 'WEBVTT\\n' > "${prefix}.vtt" + printf 'webp' > "${prefix}.orig.webp" +elif [ "$YTDLP_FAKE_MODE" = "webp-only" ]; then + printf 'webp' > "${prefix}.orig.webp" +else + printf 'WEBVTT\\n' > "${prefix}.vtt" +fi +`; + fs.writeFileSync(scriptPath, script, 'utf8'); + fs.chmodSync(scriptPath, 0o755); + return scriptPath; +} + async function withFakeYtDlp( mode: 'both' | 'webp-only' | 'multi' | 'multi-primary-only-fail' | 'rolling-auto', fn: (dir: string, binDir: string) => Promise, @@ -100,7 +189,11 @@ async function withFakeYtDlp( return await withTempDir(async (root) => { const binDir = path.join(root, 'bin'); fs.mkdirSync(binDir, { recursive: true }); - makeFakeYtDlpScript(binDir); + if (process.platform === 'win32') { + makeFakeYtDlpScript(binDir); + } else { + makeFakeYtDlpShellScript(binDir); + } const originalPath = process.env.PATH ?? ''; process.env.PATH = `${binDir}${path.delimiter}${originalPath}`; @@ -129,6 +222,11 @@ async function withFakeYtDlpCommand( process.env.YTDLP_FAKE_MODE = mode; process.env.SUBMINER_YTDLP_BIN = process.platform === 'win32' ? path.join(binDir, 'yt-dlp.cmd') : path.join(binDir, 'yt-dlp'); + if (process.platform === 'win32') { + makeFakeYtDlpScript(binDir); + } else { + makeFakeYtDlpShellScript(binDir); + } try { return await fn(root, binDir); } finally { diff --git a/src/core/services/youtube/track-probe.test.ts b/src/core/services/youtube/track-probe.test.ts index e7e93e06..b5460105 100644 --- a/src/core/services/youtube/track-probe.test.ts +++ b/src/core/services/youtube/track-probe.test.ts @@ -17,10 +17,17 @@ async function withTempDir(fn: (dir: string) => Promise): Promise { function makeFakeYtDlpScript(dir: string, payload: unknown, rawScript = false): void { const scriptPath = path.join(dir, 'yt-dlp'); const stdoutBody = typeof payload === 'string' ? payload : JSON.stringify(payload); - const script = rawScript - ? stdoutBody - : `#!/usr/bin/env node + const script = + process.platform === 'win32' + ? rawScript + ? stdoutBody + : `#!/usr/bin/env node process.stdout.write(${JSON.stringify(stdoutBody)}); +` + : `#!/usr/bin/env sh +cat <<'EOF' | base64 -d +${Buffer.from(rawScript ? stdoutBody : `${JSON.stringify(stdoutBody)}`).toString('base64')} +EOF `; fs.writeFileSync(scriptPath, script, 'utf8'); if (process.platform !== 'win32') {