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
15 changes: 12 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@8641a17e25bf5b40c118d48fe0f81e8655731839 # 1.85.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.85.0
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
key: msrv
Expand All @@ -23,8 +25,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1 # 1.95.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.95.0
components: clippy, rustfmt
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
- name: Install screenshot narrative scanner
Expand All @@ -44,13 +47,19 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1 # 1.95.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.95.0
components: clippy
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
key: windows
- name: Create protected Windows test root
run: ./scripts/setup-windows-test-volume.ps1
- run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- run: cargo test --test phase4_product f050_default_json_and_private_content_outputs_remain_isolated --locked
- run: cargo test --test phase5_scale f055_windows_timestamp_preserving_rewrite_invalidates_store --locked
- run: cargo test --test phase5_scale f056_store_is_private_corruption_is_explicit_and_rebuild_is_source_safe --locked
- name: Remove protected Windows test volume
if: always()
run: ./scripts/setup-windows-test-volume.ps1 -Detach
18 changes: 14 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@8641a17e25bf5b40c118d48fe0f81e8655731839 # 1.85.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.85.0
- run: cargo check --all-targets --locked

verify-current:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1 # 1.95.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.95.0
components: clippy, rustfmt
- name: Install repository verification tools
run: |
Expand All @@ -64,16 +67,22 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1 # 1.95.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.95.0
components: clippy
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
key: release-windows
- name: Create protected Windows test root
run: ./scripts/setup-windows-test-volume.ps1
- run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- run: cargo test --test phase4_product f050_default_json_and_private_content_outputs_remain_isolated --locked
- run: cargo test --test phase5_scale f055_windows_timestamp_preserving_rewrite_invalidates_store --locked
- run: cargo test --test phase5_scale f056_store_is_private_corruption_is_explicit_and_rebuild_is_source_safe --locked
- name: Remove protected Windows test volume
if: always()
run: ./scripts/setup-windows-test-volume.ps1 -Detach

build:
needs: [verify-tag, verify-msrv, verify-current, verify-windows]
Expand All @@ -96,8 +105,9 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1 # 1.95.0
- uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: 1.95.0
targets: ${{ matrix.target }}

- run: cargo build --release --locked --target ${{ matrix.target }}
Expand Down
94 changes: 94 additions & 0 deletions scripts/setup-windows-test-volume.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
[CmdletBinding()]
param(
[switch]$Detach
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Invoke-DiskPart {
param(
[Parameter(Mandatory)]
[string[]]$Commands
)

$scriptPath = Join-Path $env:RUNNER_TEMP (
"ccwrapped-diskpart-" + [Guid]::NewGuid().ToString("N") + ".txt"
)
try {
[System.IO.File]::WriteAllLines(
$scriptPath,
$Commands,
[System.Text.Encoding]::ASCII
)
$output = & diskpart.exe /s $scriptPath
$exitCode = $LASTEXITCODE
$output | Write-Host
if ($exitCode -ne 0) {
throw "diskpart exited with status $exitCode"
}
}
finally {
Remove-Item -LiteralPath $scriptPath -Force -ErrorAction SilentlyContinue
}
}

if ($Detach) {
$vhdPath = $env:CCWRAPPED_WINDOWS_TEST_VHD
if ([string]::IsNullOrWhiteSpace($vhdPath)) {
return
}
if (Test-Path -LiteralPath $vhdPath) {
Invoke-DiskPart -Commands @(
"select vdisk file=`"$vhdPath`"",
"detach vdisk"
)
Remove-Item -LiteralPath $vhdPath -Force -ErrorAction SilentlyContinue
}
return
}

if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP) -or
[string]::IsNullOrWhiteSpace($env:GITHUB_ENV)) {
throw "the Windows test volume requires RUNNER_TEMP and GITHUB_ENV"
}

$usedLetters = @(
Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Name }
)
$driveLetter = @("W", "V", "U", "T", "S", "R") |
Where-Object { $usedLetters -notcontains $_ } |
Select-Object -First 1
if ([string]::IsNullOrWhiteSpace($driveLetter)) {
throw "no drive letter is available for the Windows test volume"
}

$vhdPath = Join-Path $env:RUNNER_TEMP (
"ccwrapped-tests-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT.vhdx"
)
"CCWRAPPED_WINDOWS_TEST_VHD=$vhdPath" | Add-Content -Path $env:GITHUB_ENV

Invoke-DiskPart -Commands @(
"create vdisk file=`"$vhdPath`" maximum=256 type=expandable",
"select vdisk file=`"$vhdPath`"",
"attach vdisk",
"create partition primary",
"format fs=ntfs quick label=CCWRAPPED_TEST",
"assign letter=$driveLetter"
)

$volumeRoot = "${driveLetter}:\"
if (-not (Test-Path -LiteralPath $volumeRoot)) {
throw "diskpart did not mount the Windows test volume at $volumeRoot"
}

$currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$grant = "*" + $currentSid + ":(OI)(CI)F"
& icacls.exe $volumeRoot /inheritance:r /grant:r $grant
if ($LASTEXITCODE -ne 0) {
throw "failed to protect the Windows test volume root"
}

$testRoot = Join-Path $volumeRoot "fixtures"
New-Item -ItemType Directory -Path $testRoot | Out-Null
"CCWRAPPED_WINDOWS_TEST_ROOT=$testRoot" | Add-Content -Path $env:GITHUB_ENV
6 changes: 4 additions & 2 deletions tests/phase0_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ fn public_api_baseline_has_signatures_and_traits() {
}

#[test]
fn compatibility_capture_ci_uses_the_pinned_toolchain() {
assert!(CI.contains("dtolnay/rust-toolchain@f133eefe930d61f0d9371efd474daf0125ed3dd1"));
fn compatibility_capture_ci_separates_the_action_pin_from_toolchain_versions() {
assert!(CI.contains("dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c"));
assert_eq!(CI.matches("toolchain: 1.85.0").count(), 1);
assert_eq!(CI.matches("toolchain: 1.95.0").count(), 2);
assert!(CI.contains("cargo test --all-targets"));
assert!(!CI.contains("dtolnay/rust-toolchain@stable"));
}
Expand Down
33 changes: 28 additions & 5 deletions tests/phase4_product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ impl TestWorkspace {
.expect("system clock must be after Unix epoch")
.as_nanos();
let sequence = TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
#[cfg(windows)]
let base = std::env::var_os("CCWRAPPED_WINDOWS_TEST_ROOT")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.expect("Windows product tests require a test root or USERPROFILE");
#[cfg(not(windows))]
let base = std::env::temp_dir();
let root = base.join(format!(
"ccwrapped-phase4-{label}-{}-{nonce}-{sequence}",
std::process::id()
));
Expand Down Expand Up @@ -796,17 +803,29 @@ fn f050_default_json_and_private_content_outputs_remain_isolated() {

let default_dir = workspace.output_dir("default");
let default = run_ccwrapped_with_data(&default_dir, &source, &[]);
assert!(default.status.success());
assert!(
default.status.success(),
"default output failed: {}",
String::from_utf8_lossy(&default.stderr)
);
assert!(visible_output_entries(&default_dir).is_empty());

let json_dir = workspace.output_dir("json");
let json = run_ccwrapped_with_data(&json_dir, &source, &["--json"]);
assert!(json.status.success());
assert!(
json.status.success(),
"JSON output failed: {}",
String::from_utf8_lossy(&json.stderr)
);
assert!(visible_output_entries(&json_dir).is_empty());

let all_dir = workspace.output_dir("all");
let all = run_ccwrapped_with_data(&all_dir, &source, &["--all"]);
assert!(all.status.success());
assert!(
all.status.success(),
"all-formats output failed: {}",
String::from_utf8_lossy(&all.stderr)
);
assert_eq!(
visible_output_entries(&all_dir),
vec![
Expand All @@ -819,7 +838,11 @@ fn f050_default_json_and_private_content_outputs_remain_isolated() {

let archive_dir = workspace.output_dir("archive");
let archive = run_ccwrapped_with_data(&archive_dir, &source, &["--archive"]);
assert!(archive.status.success());
assert!(
archive.status.success(),
"private archive output failed: {}",
String::from_utf8_lossy(&archive.stderr)
);
assert_eq!(
visible_output_entries(&archive_dir),
vec!["wrapped-archive".to_string()]
Expand Down
28 changes: 21 additions & 7 deletions tests/phase5_scale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ impl Scratch {
.expect("clock follows epoch")
.as_nanos();
let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
#[cfg(windows)]
let base = std::env::var_os("CCWRAPPED_WINDOWS_TEST_ROOT")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.expect("Windows store tests require a test root or USERPROFILE");
#[cfg(not(windows))]
let base = std::env::temp_dir();
let path = base.join(format!(
"ccwrapped-phase5-{label}-{}-{nonce}-{id}",
std::process::id()
));
Expand Down Expand Up @@ -969,10 +976,15 @@ fn f055_windows_timestamp_preserving_rewrite_invalidates_store() {
.modified()
.expect("read original Windows last-write time");
let mut replacement = fs::read(&changed).expect("read Windows transcript");
let token_digit = replacement
.windows(b"\"input_tokens\":1".len())
.position(|window| window == b"\"input_tokens\":1")
.expect("generated input token")
let accepted_record = replacement
.windows(b"\"requestId\":\"request-00000-00072\"".len())
.position(|window| window == b"\"requestId\":\"request-00000-00072\"")
.expect("generated accepted request");
let token_digit = accepted_record
+ replacement[accepted_record..]
.windows(b"\"input_tokens\":1".len())
.position(|window| window == b"\"input_tokens\":1")
.expect("generated input token")
+ b"\"input_tokens\":".len();
replacement[token_digit] = b'9';
fs::write(&changed, replacement).expect("write equal-length Windows replacement");
Expand All @@ -982,6 +994,8 @@ fn f055_windows_timestamp_preserving_rewrite_invalidates_store() {
.expect("open Windows replacement for timestamp restore");
file.set_times(std::fs::FileTimes::new().set_modified(original_modified))
.expect("restore Windows last-write time");
// Windows finalizes file timestamps when the writing handle closes.
drop(file);
assert_eq!(
fs::metadata(&changed)
.expect("read restored Windows metadata")
Expand All @@ -1000,8 +1014,8 @@ fn f055_windows_timestamp_preserving_rewrite_invalidates_store() {
);
assert!(clean.status.success());
assert_ne!(
stored.stdout, first.stdout,
"the rewritten token did not change the stored report"
clean.stdout, first.stdout,
"the completed rewrite did not change the clean report"
);
assert_eq!(
stored.stdout, clean.stdout,
Expand Down
Loading