tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Sutichaykuna

⎇ main modules/plugin-host/tests/smoke.rs Rust · 109 siq'ikuna · 4.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat: llimphi standalone — framework UI soberano extraído del monorepo e65e9cc62 · Sergio ·

1//! Smoke tests del runtime Tier 2 — verifican:
2//!
3//! 1. Carga desde disco (`manifest.toml` + `.wasm`) e invocación que
4//!    devuelve `PluginAction::SetStatus` con el saludo concatenado.
5//! 2. Sandbox por permisos: un plugin con `filesystem = "none"` que
6//!    intenta llamar `plugin.open_at` trap-ea — el import no se
7//!    enlazó, así que el módulo importa una función inexistente.
8//! 3. Permiso concedido: el mismo plugin con `filesystem = "read-only"`
9//!    sí enlaza, ejecuta, y emite `PluginAction::OpenAt`.
10
11use std::path::PathBuf;
12
13use card_core::{FsPolicy, Permissions};
14use llimphi_plugin_host::{PluginAction, PluginError, PluginHost, PluginManifest};
15
16fn fixture_dir() -> PathBuf {
17    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello-status")
18}
19
20/// Compila el .wat del fixture a .wasm en el OUT_DIR efímero del test.
21/// Lo hacemos por test (no en build.rs) para mantener el crate sin
22/// build script — el costo es trivial y la lógica vive con el test.
23fn compile_fixture_to(dir: &std::path::Path) {
24    let wat = std::fs::read_to_string(dir.join("plugin.wat")).expect("leo plugin.wat");
25    let wasm = wat::parse_str(&wat).expect("WAT del fixture compila a wasm");
26    std::fs::write(dir.join("plugin.wasm"), wasm).expect("escribo plugin.wasm");
27}
28
29#[test]
30fn carga_desde_directorio_y_devuelve_set_status() {
31    let dir = fixture_dir();
32    compile_fixture_to(&dir);
33
34    let mut host = PluginHost::new();
35    let id = host.load_from_dir(&dir).expect("plugin carga desde dir");
36
37    let manifest = host.manifest(id).expect("manifest accesible");
38    assert_eq!(manifest.name, "hello-status");
39    assert_eq!(manifest.capabilities, vec!["status.greet".to_string()]);
40
41    let action = host.invoke(id, "status.greet", b"mundo").expect("invoke ok");
42    assert_eq!(action, PluginAction::SetStatus("hola, mundo".into()));
43
44    // El host puede enumerar capabilities agregadas para construir su Card.
45    assert_eq!(host.all_capabilities(), vec!["status.greet".to_string()]);
46}
47
48/// WAT que intenta importar `plugin.open_at`. Sirve como "plugin
49/// malicioso" para verificar el sandbox: si el host no concede
50/// `filesystem`, el linker no enlaza el import → wasmi rechaza la
51/// instanciación con un error de import faltante.
52fn wants_open_at_wat() -> &'static str {
53    // El path va en offset 256 para no colisionar con el buffer
54    // [cap | args] que el host escribe a partir del offset 0.
55    r#"
56(module
57  (import "plugin" "open_at" (func $open_at (param i32 i32 i32 i32)))
58  (memory (export "memory") 1)
59  (data (i32.const 256) "/etc/passwd")
60  (func (export "_invoke")
61        (param i32) (param i32) (param i32) (param i32)
62        (result i32)
63    (call $open_at (i32.const 256) (i32.const 11) (i32.const 10) (i32.const 5))
64    (i32.const 0)
65  )
66)
67"#
68}
69
70#[test]
71fn sin_permiso_filesystem_el_plugin_no_instancia() {
72    let bytes = wat::parse_str(wants_open_at_wat()).unwrap();
73    let manifest = PluginManifest {
74        name: "wants-fs".into(),
75        version: "0.1.0".into(),
76        capabilities: vec!["fs.open".into()],
77        permissions: Permissions::default(), // filesystem = none
78    };
79
80    let mut host = PluginHost::new();
81    let id = host.load_bytes(manifest, &bytes).expect("carga ok — el sandbox actúa al invocar");
82
83    let err = host.invoke(id, "fs.open", b"").expect_err("debe fallar sin permiso fs");
84    // wasmi reporta el import faltante en la instanciación.
85    assert!(
86        matches!(err, PluginError::Instantiate(_)),
87        "esperaba Instantiate, vi {err:?}"
88    );
89}
90
91#[test]
92fn con_permiso_filesystem_el_plugin_emite_open_at() {
93    let bytes = wat::parse_str(wants_open_at_wat()).unwrap();
94    let manifest = PluginManifest {
95        name: "wants-fs".into(),
96        version: "0.1.0".into(),
97        capabilities: vec!["fs.open".into()],
98        permissions: Permissions { filesystem: FsPolicy::ReadOnly, ..Permissions::default() },
99    };
100
101    let mut host = PluginHost::new();
102    let id = host.load_bytes(manifest, &bytes).unwrap();
103    let action = host.invoke(id, "fs.open", b"").expect("con permiso, debe correr");
104
105    assert_eq!(
106        action,
107        PluginAction::OpenAt { path: PathBuf::from("/etc/passwd"), line: 10, col: 5 }
108    );
109}