mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-31 01:04:07 +08:00
ci: run Windows backend tests against WSL2 filesystems (#6233)
* ci: add WSL2 backend checks * ci: fix WSL2 backend job by keeping MSVC toolchain on native temp - compile test binaries before pointing TEMP/TMP at the WSL2 UNC path (mt.exe fails with c1010070/LNK1327 when TEMP is a UNC path) - inject WSL2 TEMP/TMP per test step instead of via GITHUB_ENV - use --tests so doctests cannot trigger link steps under the UNC temp - run the filesystem contract gate before the full serial suite - drop fmt/clippy steps duplicated from the existing Windows job * ci: move full WSL2 suite to nightly, keep per-PR contract gate - the full serial suite needs 50+ minutes on the 9P filesystem (2026/2343 lib tests finished in 39.5min before the 45min timeout) - per-PR job now runs only the atomic-write contract (~6min total) and builds just the lib test binary via --lib - new nightly workflow (schedule + workflow_dispatch, 90min timeout) runs the contract plus the full serial suite on the WSL2 home --------- Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -105,3 +105,75 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --manifest-path src-tauri/Cargo.toml
|
||||
|
||||
backend-windows-wsl2:
|
||||
name: Backend Checks (Windows + WSL2 home)
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
||||
|
||||
- name: Setup WSL2
|
||||
uses: Vampire/setup-wsl@d1da7f2c0322a5ee4f24975344f67fc0f5baf364 # v7
|
||||
with:
|
||||
distribution: Ubuntu-24.04
|
||||
wsl-version: 2
|
||||
|
||||
- name: Cache Cargo registry and build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
src-tauri/target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
|
||||
- name: Create frontend dist placeholder
|
||||
shell: bash
|
||||
run: mkdir -p dist
|
||||
|
||||
- name: Prepare WSL2-backed test environment
|
||||
shell: pwsh
|
||||
run: |
|
||||
wsl.exe -d Ubuntu-24.04 -- bash -lc "rm -rf /tmp/cc-switch-windows-test-root && mkdir -p /tmp/cc-switch-windows-test-root/{home,temp} && chmod -R 0777 /tmp/cc-switch-windows-test-root"
|
||||
$testRoot = "\\wsl.localhost\Ubuntu-24.04\tmp\cc-switch-windows-test-root"
|
||||
$testHome = Join-Path $testRoot "home"
|
||||
$testTemp = Join-Path $testRoot "temp"
|
||||
foreach ($path in @($testRoot, $testHome, $testTemp)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "WSL2-backed test path is unavailable: $path"
|
||||
}
|
||||
}
|
||||
"CC_SWITCH_TEST_HOME=$testHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
"CC_SWITCH_WSL_TEST_DIR=$testRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
"CC_SWITCH_WSL_TEST_TEMP=$testTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Compile backend tests with native temp
|
||||
# link.exe/mt.exe cannot create manifests when TEMP points at a WSL UNC path.
|
||||
run: cargo test --lib --manifest-path src-tauri/Cargo.toml --no-run
|
||||
|
||||
- name: Run Windows-to-WSL2 filesystem contract
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:TEMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
$env:TMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
$testName = "config::tests::atomic_write_replaces_existing_wsl_unc_file"
|
||||
$testList = cargo test --lib --manifest-path src-tauri/Cargo.toml -- --ignored --list
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
$expected = "${testName}: test"
|
||||
if ($testList -notcontains $expected) {
|
||||
throw "Expected ignored test was not discovered: $testName"
|
||||
}
|
||||
cargo test --lib --manifest-path src-tauri/Cargo.toml $testName -- --ignored --exact --nocapture
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
name: WSL2 Nightly
|
||||
|
||||
# The full backend suite takes 50+ minutes on a \\wsl.localhost 9P filesystem,
|
||||
# so it runs nightly instead of per PR. The per-PR gate in ci.yml only runs the
|
||||
# atomic-write contract test against the same WSL2-backed environment.
|
||||
on:
|
||||
schedule:
|
||||
- cron: "23 18 * * *" # 02:23 Beijing time
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
backend-windows-wsl2-full:
|
||||
name: Backend Full Suite (Windows + WSL2 home)
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 90
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
||||
|
||||
- name: Setup WSL2
|
||||
uses: Vampire/setup-wsl@d1da7f2c0322a5ee4f24975344f67fc0f5baf364 # v7
|
||||
with:
|
||||
distribution: Ubuntu-24.04
|
||||
wsl-version: 2
|
||||
|
||||
- name: Cache Cargo registry and build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
src-tauri/target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
|
||||
- name: Create frontend dist placeholder
|
||||
shell: bash
|
||||
run: mkdir -p dist
|
||||
|
||||
- name: Prepare WSL2-backed test environment
|
||||
shell: pwsh
|
||||
run: |
|
||||
wsl.exe -d Ubuntu-24.04 -- bash -lc "rm -rf /tmp/cc-switch-windows-test-root && mkdir -p /tmp/cc-switch-windows-test-root/{home,temp} && chmod -R 0777 /tmp/cc-switch-windows-test-root"
|
||||
$testRoot = "\\wsl.localhost\Ubuntu-24.04\tmp\cc-switch-windows-test-root"
|
||||
$testHome = Join-Path $testRoot "home"
|
||||
$testTemp = Join-Path $testRoot "temp"
|
||||
foreach ($path in @($testRoot, $testHome, $testTemp)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "WSL2-backed test path is unavailable: $path"
|
||||
}
|
||||
}
|
||||
"CC_SWITCH_TEST_HOME=$testHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
"CC_SWITCH_WSL_TEST_DIR=$testRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
"CC_SWITCH_WSL_TEST_TEMP=$testTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Compile backend tests with native temp
|
||||
# link.exe/mt.exe cannot create manifests when TEMP points at a WSL UNC path.
|
||||
run: cargo test --tests --manifest-path src-tauri/Cargo.toml --no-run
|
||||
|
||||
- name: Run Windows-to-WSL2 filesystem contract
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:TEMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
$env:TMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
$testName = "config::tests::atomic_write_replaces_existing_wsl_unc_file"
|
||||
$testList = cargo test --tests --manifest-path src-tauri/Cargo.toml -- --ignored --list
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
$expected = "${testName}: test"
|
||||
if ($testList -notcontains $expected) {
|
||||
throw "Expected ignored test was not discovered: $testName"
|
||||
}
|
||||
cargo test --tests --manifest-path src-tauri/Cargo.toml $testName -- --ignored --exact --nocapture
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
- name: Run full test suite against WSL2-backed home
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:TEMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
$env:TMP = $env:CC_SWITCH_WSL_TEST_TEMP
|
||||
cargo test --tests --manifest-path src-tauri/Cargo.toml -- --test-threads=1
|
||||
@@ -478,6 +478,33 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), AppError> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_atomic_write_replaces_existing_file(dir: &Path) {
|
||||
let path = dir.join("atomic-write-contract.json");
|
||||
std::fs::write(&path, b"old contents").unwrap();
|
||||
|
||||
atomic_write(&path, b"new contents").unwrap();
|
||||
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"new contents");
|
||||
let tmp_prefix = "atomic-write-contract.json.tmp.";
|
||||
let leftovers: Vec<_> = std::fs::read_dir(dir)
|
||||
.unwrap()
|
||||
.map(|entry| entry.unwrap())
|
||||
.filter(|entry| entry.file_name().to_string_lossy().starts_with(tmp_prefix))
|
||||
.map(|entry| entry.path())
|
||||
.collect();
|
||||
assert!(
|
||||
leftovers.is_empty(),
|
||||
"temporary files remain: {leftovers:?}"
|
||||
);
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atomic_write_replaces_existing_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
assert_atomic_write_replaces_existing_file(dir.path());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn atomic_write_preserves_destination_when_windows_replace_fails() {
|
||||
@@ -501,6 +528,39 @@ mod tests {
|
||||
assert_eq!(std::fs::read_dir(dir.path()).unwrap().count(), 1);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
#[ignore = "requires CC_SWITCH_WSL_TEST_DIR to point to a WSL2 UNC directory"]
|
||||
fn atomic_write_replaces_existing_wsl_unc_file() {
|
||||
let root = PathBuf::from(
|
||||
std::env::var_os("CC_SWITCH_WSL_TEST_DIR").expect("CC_SWITCH_WSL_TEST_DIR must be set"),
|
||||
);
|
||||
let home = get_home_dir();
|
||||
let temp = std::env::temp_dir();
|
||||
for (name, path) in [
|
||||
("test root", root.as_path()),
|
||||
("test home", home.as_path()),
|
||||
("temporary directory", temp.as_path()),
|
||||
] {
|
||||
let unc = path.to_string_lossy();
|
||||
assert!(
|
||||
unc.starts_with(r"\\wsl.localhost\") || unc.starts_with(r"\\wsl$\"),
|
||||
"expected {name} to be a WSL UNC path, got {unc}"
|
||||
);
|
||||
assert!(
|
||||
path.starts_with(&root),
|
||||
"expected {name} to be under {}, got {unc}",
|
||||
root.display()
|
||||
);
|
||||
}
|
||||
|
||||
let dir = tempfile::Builder::new()
|
||||
.prefix("atomic-write-contract-")
|
||||
.tempdir_in(&root)
|
||||
.unwrap();
|
||||
assert_atomic_write_replaces_existing_file(dir.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_mcp_path_from_override_uses_config_dir_for_custom_path() {
|
||||
let override_dir = PathBuf::from("/tmp/profile/.claude");
|
||||
|
||||
Reference in New Issue
Block a user