sim-lib-server 0.1.0

SIM workspace package for sim lib server.
Documentation
use super::*;
use sim_kernel::{Args, Cx, ObjectCompat, Value};

#[test]
fn server_frame_payloads_roundtrip_through_installed_codecs() {
    let mut cx = cx();
    let expr = Expr::Call {
        operator: Box::new(Expr::Symbol(Symbol::new("echo"))),
        args: vec![Expr::String("wire".to_owned()), Expr::Bool(true)],
    };

    for codec in installed_codecs() {
        let frame = ServerFrame::from_expr(
            &mut cx,
            codec.clone(),
            FrameKind::Request,
            &expr,
            Consistency::RemoteOnly,
            vec![
                CapabilityName::new("server.use"),
                CapabilityName::new("codec.decode"),
            ],
            true,
        )
        .unwrap();

        let decoded = frame.decode_expr(&mut cx, ReadPolicy::default()).unwrap();
        assert_eq!(decoded, expr);
        assert_eq!(frame.codec, codec);
        assert_eq!(frame.envelope.consistency, Consistency::RemoteOnly);
        assert_eq!(
            frame.envelope.required_capabilities,
            vec![
                CapabilityName::new("server.use"),
                CapabilityName::new("codec.decode"),
            ]
        );
        assert!(frame.envelope.trace);
    }
}

#[test]
fn server_runtime_objects_are_browseable() {
    let mut cx = cx();
    let codecs = installed_codecs();
    let default_codec = codecs[0].clone();
    let site = Arc::new(LoopbackSite {
        address: ServerAddress::InProcess { thread: 1 },
        codecs: codecs.clone(),
    });

    let server = Server::new(
        ServerAddress::Local,
        default_codec.clone(),
        codecs.clone(),
        crate::ThreadMode::Coop,
        IsolationPolicy::default(),
        None,
        site.clone(),
        vec![(Symbol::new("address"), Expr::Symbol(Symbol::new("local")))],
    )
    .unwrap();
    let connection = Connection::new(
        ServerAddress::InProcess { thread: 1 },
        default_codec.clone(),
        codecs.clone(),
        site,
    )
    .unwrap();
    let frame = ServerFrame::from_expr(
        &mut cx,
        default_codec,
        FrameKind::Notify,
        &Expr::String("payload".to_owned()),
        Consistency::LocalFirst,
        Vec::new(),
        false,
    )
    .unwrap();

    let server_table = server.as_table(&mut cx).unwrap();
    let connection_table = connection.as_table(&mut cx).unwrap();
    let frame_table = frame.as_table(&mut cx).unwrap();
    let Expr::Map(server_entries) = server_table.object().as_expr(&mut cx).unwrap() else {
        panic!("server table should encode as a map");
    };

    assert!(matches!(
        connection_table.object().as_expr(&mut cx).unwrap(),
        Expr::Map(_)
    ));
    assert!(matches!(
        frame_table.object().as_expr(&mut cx).unwrap(),
        Expr::Map(_)
    ));
    assert_eq!(server.display(&mut cx).unwrap(), "#<server>");
    assert_eq!(
        table_field(&server_entries, "listening"),
        Some(Expr::Bool(false))
    );
    assert_eq!(
        table_field(&server_entries, "sessions"),
        Some(Expr::String("0".to_owned()))
    );
    assert_eq!(connection.display(&mut cx).unwrap(), "#<server-connection>");
    assert!(frame.display(&mut cx).unwrap().contains("notify"));
}

#[test]
fn server_helpers_use_codec_registry_instead_of_parallel_serialization() {
    let mut cx = cx();
    let codec = installed_codecs()[0].clone();
    let expr = Expr::String("registry".to_owned());

    let payload =
        encode_frame_payload(&mut cx, &codec, &expr, sim_kernel::EncodeOptions::default()).unwrap();
    let decoded = decode_frame_payload(
        &mut cx,
        &codec,
        &payload,
        ReadPolicy::default(),
        Default::default(),
    )
    .unwrap();

    assert_eq!(decoded, expr);
    assert!(cx.registry().codec_by_symbol(&codec).is_some());
}

#[test]
fn connection_realize_requires_eval_remote_for_remote_like_addresses() {
    let mut cx = cx();
    let codecs = installed_codecs();
    let connection = Connection::new(
        ServerAddress::InProcess { thread: 7 },
        codecs[0].clone(),
        codecs.clone(),
        Arc::new(ResponseSite {
            address: ServerAddress::Local,
            codecs,
        }),
    )
    .unwrap();

    let request = EvalRequest {
        expr: Expr::Nil,
        mode: sim_kernel::EvalMode::Eval,
        result_shape: None,
        answer_limit: None,
        stream_buffer: None,
        stream: false,
        required_capabilities: Vec::new(),
        deadline: None,
        consistency: Consistency::LocalFirst,
        trace: false,
    };
    let denied = connection.realize(&mut cx, request.clone());
    assert!(matches!(
        denied,
        Err(sim_kernel::Error::CapabilityDenied { capability })
            if capability == eval_remote_capability()
    ));

    cx.grant(eval_remote_capability());
    let reply = connection.realize(&mut cx, request).unwrap();
    assert!(matches!(
        reply.value.object().as_expr(&mut cx).unwrap(),
        Expr::Nil
    ));
}

#[test]
fn install_server_lib_is_idempotent() {
    let mut cx = cx();
    install_server_lib(&mut cx).unwrap();
    install_server_lib(&mut cx).unwrap();
    assert!(cx.registry().lib(&Symbol::new("server")).is_some());
}

#[test]
fn every_exported_server_symbol_resolves_to_a_callable() {
    let mut cx = cx();
    install_server_lib(&mut cx).unwrap();

    let manifest = &cx.registry().lib(&Symbol::new("server")).unwrap().manifest;
    for export in manifest.exports.clone() {
        let sim_kernel::Export::Function { symbol, .. } = export else {
            continue;
        };
        let value = cx.resolve_function(&symbol).unwrap();
        assert!(
            value.object().as_callable().is_some(),
            "{symbol} should resolve to a callable"
        );
    }
}

#[test]
fn server_lib_claims_loaded_cli_server_entrypoint() {
    let mut cx = cx();
    install_server_lib(&mut cx).unwrap();
    let symbol = Symbol::qualified("cli", "main/server");
    let envelope = cli_envelope(&mut cx, "server", &["server", "--dry-run"]);

    let value = cx
        .call_function(&symbol, Args::new(vec![envelope]))
        .unwrap();

    assert!(value.object().truth(&mut cx).unwrap());
}

#[test]
fn server_connect_supports_server_values_and_local_addresses() {
    let mut cx = cx();
    install_server_lib(&mut cx).unwrap();

    let server = cx
        .eval_expr(Expr::Call {
            operator: Box::new(Expr::Symbol(Symbol::qualified("server", "start"))),
            args: vec![],
        })
        .unwrap();
    cx.registry_mut()
        .register_value(Symbol::qualified("test", "server"), server.clone())
        .unwrap();

    let from_server = cx
        .call_exprs(
            cx.resolve_function(&Symbol::qualified("server", "connect"))
                .unwrap(),
            vec![Expr::Symbol(Symbol::qualified("test", "server"))],
        )
        .unwrap();
    let server_connection = from_server.object().downcast_ref::<Connection>().unwrap();
    assert_eq!(server_connection.address(), &ServerAddress::Local);

    let from_local = cx
        .call_exprs(
            cx.resolve_function(&Symbol::qualified("server", "connect"))
                .unwrap(),
            vec![quoted(Expr::Symbol(Symbol::new("local")))],
        )
        .unwrap();
    let local_connection = from_local.object().downcast_ref::<Connection>().unwrap();
    assert_eq!(local_connection.address(), &ServerAddress::Local);
}

fn cli_envelope(cx: &mut Cx, verb: &str, args: &[&str]) -> Value {
    let verb = cx.factory().string(verb.to_owned()).unwrap();
    let args = cx
        .factory()
        .list(
            args.iter()
                .map(|arg| cx.factory().string((*arg).to_owned()).unwrap())
                .collect(),
        )
        .unwrap();
    cx.factory()
        .table(vec![
            (Symbol::new("verb"), verb),
            (Symbol::new("args"), args),
        ])
        .unwrap()
}

#[test]
fn server_connect_resolves_pipeline_addresses_to_single_connections() {
    let mut cx = cx();
    install_server_lib(&mut cx).unwrap();

    let pipeline = cx
        .call_exprs(
            cx.resolve_function(&Symbol::qualified("server", "connect"))
                .unwrap(),
            vec![quoted(Expr::List(vec![
                Expr::Symbol(Symbol::new("pipeline")),
                Expr::Symbol(Symbol::new("local")),
                Expr::Symbol(Symbol::new("local")),
            ]))],
        )
        .unwrap();
    let connection = pipeline.object().downcast_ref::<Connection>().unwrap();
    assert!(matches!(
        connection.address(),
        ServerAddress::Pipeline { steps } if steps.len() == 2
    ));
}

#[test]
fn parses_full_address_and_isolation_surfaces() {
    let address = ServerAddress::from_expr(&Expr::List(vec![
        Expr::Symbol(Symbol::new("tcp")),
        Expr::Symbol(Symbol::new(":host")),
        Expr::String("0.0.0.0".to_owned()),
        Expr::Symbol(Symbol::new(":port")),
        Expr::String("5555".to_owned()),
    ]))
    .unwrap();
    assert_eq!(
        address,
        ServerAddress::Tcp {
            host: "0.0.0.0".to_owned(),
            port: 5555,
        }
    );

    let isolation = IsolationPolicy::from_expr(&Expr::List(vec![
        Expr::Symbol(Symbol::new(":env")),
        Expr::Symbol(Symbol::new("child")),
        Expr::Symbol(Symbol::new(":libs")),
        Expr::Symbol(Symbol::new("share")),
        Expr::Symbol(Symbol::new(":factory")),
        Expr::Symbol(Symbol::new("isolate")),
        Expr::Symbol(Symbol::new(":registry")),
        Expr::List(vec![
            Expr::Symbol(Symbol::new("import")),
            Expr::Symbol(Symbol::qualified("snap", "registry")),
        ]),
        Expr::Symbol(Symbol::new(":capabilities")),
        Expr::Symbol(Symbol::new("share")),
    ]))
    .unwrap();
    assert!(matches!(isolation.env, crate::ShareMode::Child));
    assert!(matches!(isolation.factory, crate::ShareMode::Isolate));
    assert!(matches!(
        isolation.registry,
        crate::ShareMode::Import(symbol) if symbol == Symbol::qualified("snap", "registry")
    ));
}