Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing rid store rust code without Flutter #15

Open
chertov opened this issue Aug 24, 2021 · 1 comment
Open

Testing rid store rust code without Flutter #15

chertov opened this issue Aug 24, 2021 · 1 comment
Labels
feature request Nothing broken; request for a new capability.

Comments

@chertov
Copy link
Collaborator

chertov commented Aug 24, 2021

I'm trying to find a way how to test code with rid store, but without Flutter.
My demo is little bit ugly..
Is there a more convenient way, perhaps?
It may be worth considering adding more convenient testing tools in the future?

use rid::RidStore;

#[cfg(not(test))]
macro_rules! rid_post {
    ($t:tt) =>      ({ let val = $t; rid::post(val) });
    ($t:expr) =>    ({ let val = $t; rid::post(val) });
    ($t:block) =>   ({ let val = $t; rid::post(val) });
}
#[cfg(test)]
macro_rules! rid_post {
    ($t:tt) =>      ({ let val = $t; crate::store::tests::rid_post(val) });
    ($t:expr) =>    ({ let val = $t; crate::store::tests::rid_post(val) });
    ($t:block) =>   ({ let val = $t; crate::store::tests::rid_post(val) });
}

#[rid::model]
#[derive(Debug, Clone, PartialEq)]
pub enum ConnState {
    Connected,
    Disconnected
}

#[rid::store]
#[rid::enums(ConnState)]
#[derive(Debug, Clone)]
pub struct Store {
    running: bool,
    count: u32,
    connection_state: ConnState,
}

impl RidStore<Msg> for Store {
    fn create() -> Self {
        std::thread::spawn(|| {
            while store::read().running {
                std::thread::sleep(std::time::Duration::from_secs(1));
                store::write().connection_state = ConnState::Connected;
                rid_post!(Reply::ConnectionStateUpdate);
                std::thread::sleep(std::time::Duration::from_secs(1));
                store::write().connection_state = ConnState::Disconnected;
                rid_post!(Reply::ConnectionStateUpdate);
            }
        });

        Self {
            running: true,
            count: 0,
            connection_state: ConnState::Disconnected,
        }
    }

    fn update(&mut self, req_id: u64, msg: Msg) {
        match msg {
            Msg::Inc => {
                self.count += 1;
                rid_post!(Reply::Increased(req_id));
            }
        }
    }
}

#[rid::message(Reply)]
pub enum Msg { Inc }

#[rid::reply]
#[derive(Debug, PartialEq)]
pub enum Reply {
    ConnectionStateUpdate,
    Increased(u64),
}

#[cfg(test)]
mod tests {
    use super::*;

    fn update(req_id: u64, msg: Msg) { store::write().update(req_id, msg) }
    fn get() -> Store { store::read().clone() }
    fn set() -> std::sync::RwLockWriteGuard<'static, Store, > { store::write() }

    #[tokio::test]
    async fn devices() -> Result<(), anyhow::Error> {
        let mut rx = {
            let (tx, rx) = futures::channel::mpsc::channel::<Reply>(5);
            RID_CHANNEL.write().replace(tx);
            rx
        };

        let req_id = 1;
        update(req_id, Msg::Inc);
        assert_eq!(rx.next().await.unwrap(), Reply::Increased(req_id));
        assert_eq!(get().count, 1);
        update(req_id, Msg::Inc);
        assert_eq!(rx.next().await.unwrap(), Reply::Increased(req_id));
        assert_eq!(get().count, 2);
        assert_eq!(rx.next().await.unwrap(), Reply::ConnectionStateUpdate);
        assert_eq!(get().connection_state, ConnState::Connected);
        assert_eq!(rx.next().await.unwrap(), Reply::ConnectionStateUpdate);
        assert_eq!(get().connection_state, ConnState::Disconnected);
        set().running = false;

        Ok(())
    }


    use std::sync::Arc;
    use parking_lot::RwLock;
    use futures::StreamExt;
    lazy_static! {
        static ref RID_CHANNEL: Arc<RwLock<Option<futures::channel::mpsc::Sender<Reply>>>> = Arc::new(RwLock::new(None));
    }
    pub fn rid_post(msg: Reply) {
        RID_CHANNEL.read().clone().unwrap().start_send(msg).unwrap();
    }
}
@chertov chertov added the feature request Nothing broken; request for a new capability. label Aug 24, 2021
@thlorenz
Copy link
Owner

thlorenz commented Aug 28, 2021

Sorry for replying late, I had lost track of my github notifications :)

This is an awesome idea. I hadn't thought about this as I'm currently testing rid itself with some integration tests which are basically mini apps. I'm testing them via Dart tests.
If you're just trying to avoid having to run Flutter to run tests you could do something similar to that. Those tests are here.

However those run somewhat slow and I bet the ones you're creating run a lot faster. Also they allow you to debug your app since it's running rust directly (something that isn't currently possible when running things via dart).

So I totally love it!

Multiple Steps

1. Example Prototype

What I'd suggest is to create a sample app with those tests inside the rid-examples repo. Include all the boilerplate to make it work there and submit a PR. This can serve as an example for others to do the same.

2. Building this into Rid

Once we got this example we can look at what parts make sense to include with rid and expose via a testing API.

3. Simplify Example

Once we got that rid test API we can simplify the example to just use that instead.

Implementation

I'm not a huge fan of more macros especially since the rust-analyzer provides very limited intellisense for those still. If we can have functions instead, but we can discuss details with the prototype PR (which you should just file in draft mode once you even got the minimum working so we can iterate on the idea).

Also it'd be nice to not call update directly, but somehow use the messaging API instead. You basically just need to get a hold of the Isolate and communicate with that. A good starting point to see how this works is to run cargo expand in a simple example and see what message related code gets generated.
That way the tests run even closer to how the app would run with Dart/Flutter.

So I'm looking forward to the initial implementation in the PR. It's better to discuss directly on code and that way I can also branch off and commit some ideas + help out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Nothing broken; request for a new capability.
Projects
None yet
Development

No branches or pull requests

2 participants