axbuild 0.4.20

An OS build lib toolkit used by arceos
Documentation
use tempfile::tempdir;

use super::*;

#[test]
fn part_path_uses_dot_part_suffix() {
    let path = Path::new("/tmp/rootfs-x86_64-alpine.img.tar.gz");
    assert_eq!(
        part_path(path),
        PathBuf::from("/tmp/rootfs-x86_64-alpine.img.tar.gz.part")
    );
}

#[test]
fn lock_path_uses_dot_lock_suffix() {
    let path = Path::new("/tmp/rootfs-x86_64-alpine.img.tar.gz");
    assert_eq!(
        lock_path(path),
        PathBuf::from("/tmp/rootfs-x86_64-alpine.img.tar.gz.lock")
    );
}

#[test]
fn github_release_asset_ref_parses_release_download_url() {
    let asset_ref = github_release_asset_ref(
        "https://github.com/rcore-os/tgosimages/releases/download/v0.0.8/rootfs-loongarch64-alpine.img.tar.xz",
    )
    .unwrap();

    assert_eq!(
        asset_ref.api_url,
        "https://api.github.com/repos/rcore-os/tgosimages/releases/tags/v0.0.8"
    );
    assert_eq!(asset_ref.asset_name, "rootfs-loongarch64-alpine.img.tar.xz");
}

#[test]
fn github_release_asset_ref_rejects_non_release_download_url() {
    assert!(github_release_asset_ref("https://example.com/rootfs.tar.xz").is_none());
    assert!(github_release_asset_ref("https://github.com/rcore-os/tgosimages").is_none());
}

#[test]
fn classify_download_sha256_accepts_matching_github_asset_digest() {
    let actual = "25443ad55c76d810532f24f6868ce66923f58b423b3d6253cec03e8c4cc4c882";
    let stale_registry = "67dd38677005c55bd5e27062bb885cb3962061729d3ca933faa146dc5d17f6b9";

    assert_eq!(
        classify_download_sha256(actual, stale_registry, Some(actual)),
        VerifyOutcome::MatchedGitHubAsset
    );
}

#[tokio::test]
async fn recoverable_lock_accepts_dead_process_pid() {
    let workspace = tempdir().unwrap();
    let lock_path = workspace.path().join("download.lock");
    fs::write(&lock_path, "pid=999999\n").unwrap();

    assert!(recoverable_lock(&lock_path).await.unwrap());
}

#[tokio::test]
async fn download_file_resumes_partial_download() {
    let server = TestServer::start_with_range_support(b"abcdef".to_vec(), true).await;
    let workspace = tempdir().unwrap();
    let output_path = workspace.path().join("rootfs.img.tar.gz");
    let part_path = part_path(&output_path);
    fs::write(&part_path, b"abc").unwrap();

    let client = http_client().unwrap();
    download_file(&client, &server.url(), &output_path)
        .await
        .unwrap();

    assert_eq!(fs::read(&output_path).unwrap(), b"abcdef");
    assert_eq!(server.last_range_header().as_deref(), Some("bytes=3-"));
}

#[tokio::test]
async fn download_file_restarts_when_range_is_ignored() {
    let server = TestServer::start_with_range_support(b"abcdef".to_vec(), false).await;
    let workspace = tempdir().unwrap();
    let output_path = workspace.path().join("rootfs.img.tar.gz");
    let part_path = part_path(&output_path);
    fs::write(&part_path, b"abc").unwrap();

    let client = http_client().unwrap();
    download_file(&client, &server.url(), &output_path)
        .await
        .unwrap();

    assert_eq!(fs::read(&output_path).unwrap(), b"abcdef");
    assert_eq!(server.last_range_header().as_deref(), Some("bytes=3-"));
}

#[tokio::test]
async fn download_file_restarts_when_range_is_invalid() {
    let server = TestServer::start_with_invalid_range(b"abcdef".to_vec()).await;
    let workspace = tempdir().unwrap();
    let output_path = workspace.path().join("rootfs.img.tar.gz");
    let part_path = part_path(&output_path);
    fs::write(&part_path, b"abcdefghi").unwrap();

    let client = http_client().unwrap();
    download_file(&client, &server.url(), &output_path)
        .await
        .unwrap();

    assert_eq!(fs::read(&output_path).unwrap(), b"abcdef");
    assert_eq!(server.request_count(), 2);
}

#[tokio::test]
async fn download_file_retries_transient_http_status() {
    let server = TestServer::start_with_failures(
        b"abcdef".to_vec(),
        vec![StatusCode::TOO_MANY_REQUESTS, StatusCode::GATEWAY_TIMEOUT],
    )
    .await;
    let workspace = tempdir().unwrap();
    let output_path = workspace.path().join("rootfs.img.tar.gz");

    let client = http_client().unwrap();
    download_file(&client, &server.url(), &output_path)
        .await
        .unwrap();

    assert_eq!(fs::read(&output_path).unwrap(), b"abcdef");
    assert_eq!(server.request_count(), 3);
}

#[tokio::test]
async fn download_file_does_not_retry_permanent_http_status() {
    let server =
        TestServer::start_with_failures(b"abcdef".to_vec(), vec![StatusCode::NOT_FOUND]).await;
    let workspace = tempdir().unwrap();
    let output_path = workspace.path().join("rootfs.img.tar.gz");

    let client = http_client().unwrap();
    let err = download_file(&client, &server.url(), &output_path)
        .await
        .unwrap_err();

    assert!(err.to_string().contains("HTTP 404 Not Found"));
    assert_eq!(server.request_count(), 1);
}

struct TestServer {
    handle: test_support::MockHandle,
}

impl TestServer {
    async fn start_with_failures(body: Vec<u8>, statuses: Vec<StatusCode>) -> Self {
        Self {
            handle: test_support::register_download_with_failures(
                "rootfs.img.tar.gz",
                body,
                test_support::MockRangeMode::Ignore,
                statuses,
            ),
        }
    }

    async fn start_with_invalid_range(body: Vec<u8>) -> Self {
        Self::start_inner(body, RangeMode::RejectInvalid).await
    }

    async fn start_with_range_support(body: Vec<u8>, support_range: bool) -> Self {
        let mode = if support_range {
            RangeMode::Support
        } else {
            RangeMode::Ignore
        };
        Self::start_inner(body, mode).await
    }

    async fn start_inner(body: Vec<u8>, range_mode: RangeMode) -> Self {
        let range_mode = match range_mode {
            RangeMode::Ignore => test_support::MockRangeMode::Ignore,
            RangeMode::Support => test_support::MockRangeMode::Support,
            RangeMode::RejectInvalid => test_support::MockRangeMode::RejectInvalid,
        };
        Self {
            handle: test_support::register_download("rootfs.img.tar.gz", body, range_mode),
        }
    }

    fn url(&self) -> String {
        self.handle.url().to_string()
    }

    fn request_count(&self) -> usize {
        self.handle.request_count()
    }

    fn last_range_header(&self) -> Option<String> {
        self.handle.last_range_header()
    }
}

#[derive(Clone, Copy)]
enum RangeMode {
    Ignore,
    Support,
    RejectInvalid,
}