ic-testkit 0.76.0

PocketIC-oriented test utilities for IC canister tests
Documentation
use super::*;
use ic_host_artifacts::wasm::validate;

const LIMITS: RawReplyLimits = RawReplyLimits {
    methods: 3,
    reply_bytes: 70_000,
    method_name_bytes: 100,
};

#[test]
fn validates_stateful_slots_and_opaque_page_spanning_replies() {
    let large = vec![0xff; 65_537];
    let methods = [
        RawReplyMethod {
            name: "read\"\\é",
            mode: ReplyMode::Update,
            reply: &large,
        },
        RawReplyMethod {
            name: "read\"\\é",
            mode: ReplyMode::Update,
            reply: b"second\0",
        },
        RawReplyMethod {
            name: "other",
            mode: ReplyMode::Update,
            reply: &[],
        },
    ];
    let wasm = module(
        &methods,
        LIMITS,
        Some(("https://ic-testkit.invalid/test/", 50_000_000_000)),
        None,
        100_000,
    )
    .unwrap();
    validate(&wasm, 100_000).unwrap();
    assert!(wasm.windows(large.len()).any(|bytes| bytes == large));
    validate(&module(&[], LIMITS, None, None, 10_000).unwrap(), 10_000).unwrap();
}

#[test]
fn refuses_catalog_and_compiled_output_limits() {
    let method = RawReplyMethod {
        name: "read",
        mode: ReplyMode::Update,
        reply: b"raw",
    };
    for limits in [
        RawReplyLimits {
            methods: 0,
            ..LIMITS
        },
        RawReplyLimits {
            reply_bytes: 2,
            ..LIMITS
        },
        RawReplyLimits {
            method_name_bytes: 3,
            ..LIMITS
        },
    ] {
        assert!(matches!(
            module(&[method], limits, None, None, 10_000),
            Err(HeldReplyError::Catalog(RawReplyError::Budget { .. }))
        ));
    }
    assert!(matches!(
        module(
            &[RawReplyMethod {
                mode: ReplyMode::Query,
                ..method
            }],
            LIMITS,
            None,
            None,
            10_000
        ),
        Err(HeldReplyError::Query { slot: 0 })
    ));
    for name in ["", "bad\0", "bad\n"] {
        assert!(matches!(
            module(
                &[RawReplyMethod { name, ..method }],
                LIMITS,
                None,
                None,
                10_000
            ),
            Err(HeldReplyError::Catalog(RawReplyError::InvalidName {
                index: 0
            }))
        ));
    }
    assert!(matches!(
        module(&[method], LIMITS, None, None, 1),
        Err(HeldReplyError::Compile(CompileError::ModuleLimit { .. }))
    ));
    let mut offset = usize::MAX;
    assert!(segment(&mut String::new(), &mut offset, b"x").is_err());
}

#[test]
fn validates_cycle_caps_without_changing_existing_catalog_admission() {
    let methods = [RawReplyMethod {
        name: "paid",
        mode: ReplyMode::Update,
        reply: b"opaque\0\xff",
    }];
    for cap in [0, 1, 1 << 65, u128::MAX] {
        for barrier in [
            None,
            Some(("https://ic-testkit.invalid/test/", 50_000_000_000)),
        ] {
            let wasm = module(&methods, LIMITS, barrier, Some(cap), 10_000).unwrap();
            validate(&wasm, 10_000).unwrap();
            assert!(
                wasm.windows(20)
                    .any(|bytes| bytes == b"msg_cycles_accept128")
            );
        }
    }
    for limits in [
        RawReplyLimits {
            methods: 0,
            ..LIMITS
        },
        RawReplyLimits {
            reply_bytes: 0,
            ..LIMITS
        },
    ] {
        assert!(matches!(
            module(&methods, limits, None, Some(0), 10_000),
            Err(HeldReplyError::Catalog(RawReplyError::Budget { .. }))
        ));
    }
}