embly-wrapper 0.0.2

The embly wrapper executable
Documentation
extern crate embly_wrapper;
#[macro_use]
extern crate lazy_static;

use {
    embly_wrapper::{
        bytes::as_u32_le,
        context::{next_message, write_msg},
        error::Result,
        instance::Instance,
        protos::comms::Message,
    },
    lucet_runtime_internals::val,
    std::{
        fs::File,
        io::prelude::*,
        os::unix::net::UnixStream,
        path::{Path, PathBuf},
        process::Command,
        str,
        sync::Mutex,
        thread, time,
    },
};

lazy_static! {
    static ref BUILD_LOCK: Mutex<usize> = Mutex::new(0);
}

fn compile_and_create_instance(name: &str, code: &str) -> Result<(Instance, UnixStream)> {
    let _lock = BUILD_LOCK.lock();
    {
        let mut file = File::create("../tests/basic_app/src/main.rs")?;
        file.write_all(code.as_bytes())?;
        // file closes
    }
    println!("{:?}", std::env::current_exe()?.as_path());
    // this makes this test work anywhere within the embly repo, but the rust wrapper
    // won't work without the cargo compiler flags within the wrapper project

    // TODO: support cargo_target_dir

    let basic_app_path = match Path::join(
        std::env::current_exe()?.as_path().parent().unwrap(),
        PathBuf::from("../../../../tests/basic_app"),
    )
    .canonicalize()
    {
        Ok(p) => format!("{}", p.as_path().display()).to_string(),
        Err(_) => {
            println!("falling back to project path in a container");
            String::from("/opt/tests/basic_app")
        }
    };

    let output = Command::new("bash")
        .args(&[
            "-c",
            format!(
                "
    cd {} \
    && set -ex \
    && rustup run nightly-2019-11-24 cargo build \
        --target wasm32-wasi \
        --release \
        -Z unstable-options \
        --out-dir ../scratch \
    && lucetc \
        --bindings ../../embly-wrapper-rs/bindings.json \
        --emit=so \
        --output ../scratch/{}.out \
        ../scratch/basic_app.wasm
        ",
                basic_app_path, name
            )
            .as_str(),
        ])
        .output()?;
    if !output.status.success() {
        let output = str::from_utf8(&output.stderr).unwrap();
        println!("{}", output);
        panic!("build failed");
    }
    println!("Compilation complete for {}", name);
    let module = String::from(format!("../tests/scratch/{}.out", name));
    let addr = String::from("1001");
    let (mut sock1, sock2) = UnixStream::pair()?;

    let mut start_msg = Message::new();
    start_msg.set_parent_address(1000);
    start_msg.set_your_address(1001);
    write_msg(&mut sock1, start_msg)?;
    Ok((Instance::new(module, addr, sock2)?, sock1))
}

fn assert_addr(master_socket: &mut UnixStream) -> Result<()> {
    let mut size_bytes: [u8; 8] = [0; 8];
    master_socket.read_exact(&mut size_bytes)?;
    println!("{:?}", size_bytes);
    let addr = as_u32_le(&size_bytes) as usize;
    assert_eq!(addr, 1001);
    Ok(())
}

#[test]
fn test_async_basic() -> Result<()> {
    let val = "ok then";
    let code = r#"
extern crate embly;

use embly::prelude::*;
use embly::Error;

async fn execute_async(mut conn: embly::Conn) {
    conn.write_all(b"ok then")
        .expect("should be able to write");
}

fn main() -> Result<(), Error> {
    embly::run(execute_async);
    Ok(())
}
"#;

    let (mut instance, mut master_socket) = compile_and_create_instance("my_face", code)?;
    instance
        .inst
        .run("main", &[val::Val::I32(0), val::Val::I32(0)])?;
    instance.send_exit_message(0)?;

    assert_addr(&mut master_socket)?;

    let msg = next_message(&mut master_socket)?;
    assert_eq!(msg.data, val.as_bytes());

    Ok(())
}

fn send_with_delay(msg: Message, mut stream: UnixStream, delay_ms: u64) {
    thread::spawn(move || {
        thread::sleep(time::Duration::from_millis(delay_ms));
        write_msg(&mut stream, msg).expect("should send");
    });
}

#[test]
fn test_async_full() -> Result<()> {
    let code = r#"
extern crate embly;

use embly::{prelude::*, spawn_function, Error};
use std::time;

async fn run_with_result(mut conn: embly::Conn) -> Result<(), Error> {
    let now = time::Instant::now();
    println!("starting {:?} s", time::Instant::now() - now);
    conn.write_all(b"ok then")?;

    let mut first = spawn_function("first")?;
    let mut second = spawn_function("second")?;

    first.await?;
    println!("first string {} {:?} s", first.string()?, time::Instant::now() - now);
    second.await?;
    println!("second string {} {:?} s", second.string()?, time::Instant::now() - now);
    let foo = 5;

    let mut bar = foo + 8;
    bar += 1;


    println!("{}", bar);

    Ok(())
}

async fn execute_async(conn: embly::Conn) {
    run_with_result(conn).await.expect("oh no");
}

fn main() -> Result<(), Error> {
    embly::run(execute_async);
    Ok(())
}    
"#;

    let (mut instance, mut master_socket) = compile_and_create_instance("async_full", code)?;
    thread::spawn(move || {
        assert_addr(&mut master_socket).expect("should");
        loop {
            if let Ok(msg) = next_message(&mut master_socket) {
                if msg.spawn_address == 0 {
                    continue;
                }
                println!("{:?}", msg);
                let mut resp_msg = Message::new();
                resp_msg.set_from(msg.spawn_address);
                resp_msg.set_to(msg.from);
                resp_msg.set_data(msg.spawn.as_bytes().to_vec());
                if msg.spawn == "first" {
                    send_with_delay(
                        resp_msg,
                        master_socket.try_clone().expect("should be able to clone"),
                        2000,
                    );
                } else if msg.spawn == "second" {
                    send_with_delay(
                        resp_msg,
                        master_socket.try_clone().expect("should be able to clone"),
                        1000,
                    );
                } else {
                    unreachable!("we only have two message types")
                }
            } else {
                println!("___ broken");
                break;
            }
        }
    });

    let now = time::Instant::now();
    instance
        .inst
        .run("main", &[val::Val::I32(0), val::Val::I32(0)])?;
    instance.send_exit_message(0)?;
    println!("run complete {:?}", time::Instant::now() - now);
    assert!((time::Instant::now() - now) < time::Duration::from_secs(3));
    Ok(())
}