Skip to main content

graphix_package_core/
testing.rs

1use anyhow::Result;
2use graphix_compiler::expr::ModuleResolver;
3use graphix_rt::{GXConfig, GXEvent, GXHandle, GXRt, NoExt};
4use poolshark::global::GPooled;
5use tokio::sync::mpsc;
6
7pub struct TestCtx {
8    pub internal_only: netidx::InternalOnly,
9    pub rt: GXHandle<NoExt>,
10}
11
12impl TestCtx {
13    pub async fn shutdown(self) {
14        drop(self.rt);
15        self.internal_only.shutdown().await
16    }
17}
18
19pub type RegisterFn = fn(
20    &mut graphix_compiler::ExecCtx<GXRt<NoExt>, <NoExt as graphix_rt::GXExt>::UserEvent>,
21    &mut fxhash::FxHashMap<netidx_core::path::Path, arcstr::ArcStr>,
22    &mut graphix_package::IndexSet<arcstr::ArcStr>,
23) -> Result<()>;
24
25pub async fn init_with_resolvers(
26    sub: mpsc::Sender<GPooled<Vec<GXEvent>>>,
27    register: &[RegisterFn],
28    mut resolvers: Vec<ModuleResolver>,
29) -> Result<TestCtx> {
30    let _ = env_logger::try_init();
31    let env = netidx::InternalOnly::new().await?;
32    let mut ctx = graphix_compiler::ExecCtx::new(GXRt::<NoExt>::new(
33        env.publisher().clone(),
34        env.subscriber().clone(),
35    ))?;
36    let mut modules = fxhash::FxHashMap::default();
37    let mut root_mods = graphix_package::IndexSet::new();
38    for f in register {
39        f(&mut ctx, &mut modules, &mut root_mods)?;
40    }
41    let mut parts = Vec::new();
42    for name in &root_mods {
43        if name == "core" {
44            parts.push(format!("mod core;\nuse core"));
45        } else {
46            parts.push(format!("mod {name}"));
47        }
48    }
49    let root = arcstr::ArcStr::from(parts.join(";\n"));
50    resolvers.insert(0, ModuleResolver::VFS(modules));
51    Ok(TestCtx {
52        internal_only: env,
53        rt: GXConfig::builder(ctx, sub)
54            .root(root)
55            .resolvers(resolvers)
56            .build()?
57            .start()
58            .await?,
59    })
60}
61
62pub async fn init(
63    sub: mpsc::Sender<GPooled<Vec<GXEvent>>>,
64    register: &[RegisterFn],
65) -> Result<TestCtx> {
66    init_with_resolvers(sub, register, vec![]).await
67}
68
69pub use graphix_compiler::expr::parser::GRAPHIX_ESC;
70pub use poolshark::local::LPooled;
71
72pub fn escape_path(path: std::path::Display) -> LPooled<String> {
73    use std::fmt::Write;
74    let mut buf: LPooled<String> = LPooled::take();
75    let mut res: LPooled<String> = LPooled::take();
76    write!(buf, "{path}").unwrap();
77    GRAPHIX_ESC.escape_to(&*buf, &mut res);
78    res
79}
80
81#[macro_export]
82macro_rules! run {
83    ($name:ident, $code:expr, $pred:expr) => {
84        $crate::run!($name, $pred, "/test.gx" => format!("let result = {}", $code));
85    };
86    ($name:ident, $pred:expr, $($path:literal => $code:expr),+) => {
87        #[tokio::test(flavor = "current_thread")]
88        async fn $name() -> ::anyhow::Result<()> {
89            let (tx, mut rx) = ::tokio::sync::mpsc::channel(10);
90            let tbl = ::fxhash::FxHashMap::from_iter([
91                $((::netidx_core::path::Path::from($path), ::arcstr::ArcStr::from($code))),+
92            ]);
93            let resolver = ::graphix_compiler::expr::ModuleResolver::VFS(tbl);
94            let ctx = $crate::testing::init_with_resolvers(
95                tx, &crate::TEST_REGISTER, vec![resolver],
96            ).await?;
97            let bs = &ctx.rt;
98            match bs.compile(::arcstr::literal!("{ mod test; test::result }")).await {
99                Err(e) => assert!($pred(dbg!(Err(e)))),
100                Ok(e) => {
101                    dbg!("compilation succeeded");
102                    let eid = e.exprs[0].id;
103                    loop {
104                        match rx.recv().await {
105                            None => ::anyhow::bail!("runtime died"),
106                            Some(mut batch) => {
107                                for e in batch.drain(..) {
108                                    match e {
109                                        ::graphix_rt::GXEvent::Env(_) => (),
110                                        ::graphix_rt::GXEvent::Updated(id, v) => {
111                                            eprintln!("{v}");
112                                            assert_eq!(id, eid);
113                                            assert!($pred(Ok(&v)));
114                                            return Ok(());
115                                        }
116                                    }
117                                }
118                            }
119                        }
120                    }
121                }
122            }
123            ctx.shutdown().await;
124            Ok(())
125        }
126    };
127}
128
129#[macro_export]
130macro_rules! run_with_tempdir {
131    (
132        name: $test_name:ident,
133        code: $code:literal,
134        setup: |$temp_dir:ident| $setup:block,
135        expect_error
136    ) => {
137        $crate::run_with_tempdir! {
138            name: $test_name,
139            code: $code,
140            setup: |$temp_dir| $setup,
141            expect: |v: ::netidx::subscriber::Value| -> ::anyhow::Result<()> {
142                if matches!(v, ::netidx::subscriber::Value::Error(_)) {
143                    Ok(())
144                } else {
145                    panic!("expected Error value, got: {v:?}")
146                }
147            }
148        }
149    };
150    (
151        name: $test_name:ident,
152        code: $code:literal,
153        setup: |$temp_dir:ident| $setup:block,
154        verify: |$verify_dir:ident| $verify:block
155    ) => {
156        $crate::run_with_tempdir! {
157            name: $test_name,
158            code: $code,
159            setup: |$temp_dir| $setup,
160            expect: |v: ::netidx::subscriber::Value| -> ::anyhow::Result<()> {
161                if !matches!(v, ::netidx::subscriber::Value::Null) {
162                    panic!("expected Null (success), got: {v:?}");
163                }
164                Ok(())
165            },
166            verify: |$verify_dir| $verify
167        }
168    };
169    (
170        name: $test_name:ident,
171        code: $code:literal,
172        setup: |$temp_dir:ident| $setup:block,
173        expect: $expect_payload:expr
174        $(, verify: |$verify_dir:ident| $verify:block)?
175    ) => {
176        #[tokio::test(flavor = "current_thread")]
177        async fn $test_name() -> ::anyhow::Result<()> {
178            let (tx, mut rx) = ::tokio::sync::mpsc::channel::<
179                ::poolshark::global::GPooled<Vec<::graphix_rt::GXEvent>>
180            >(10);
181            let ctx = $crate::testing::init(tx, &crate::TEST_REGISTER).await?;
182            let $temp_dir = ::tempfile::tempdir()?;
183
184            let test_file = { $setup };
185
186            let code = format!(
187                $code,
188                $crate::testing::escape_path(test_file.display())
189            );
190            let compiled = ctx.rt.compile(::arcstr::ArcStr::from(code)).await?;
191            let eid = compiled.exprs[0].id;
192
193            let timeout = ::tokio::time::sleep(::std::time::Duration::from_secs(2));
194            ::tokio::pin!(timeout);
195
196            loop {
197                ::tokio::select! {
198                    _ = &mut timeout => panic!("timeout waiting for result"),
199                    Some(mut batch) = rx.recv() => {
200                        for event in batch.drain(..) {
201                            if let ::graphix_rt::GXEvent::Updated(id, v) = event {
202                                if id == eid {
203                                    $expect_payload(v)?;
204                                    $(
205                                        let $verify_dir = &$temp_dir;
206                                        $verify
207                                    )?
208                                    return Ok(());
209                                }
210                            }
211                        }
212                    }
213                }
214            }
215        }
216    };
217}