sapphire-lang 0.8.0

Gradually typed scripting language where every value is an object and types are optional, checked at runtime
Documentation
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;

use super::{eval, eval_err};
use sapphire::vm::{VmError, VmValue};

fn with_server<F>(handler: impl Fn(std::net::TcpStream) + Send + 'static, f: F)
where
    F: FnOnce(u16),
{
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = listener.local_addr().unwrap().port();
    thread::spawn(move || {
        if let Ok((stream, _)) = listener.accept() {
            handler(stream);
        }
    });
    f(port);
}

#[test]
fn connect_write_read_all() {
    with_server(
        |mut stream| {
            let mut buf = vec![0u8; 64];
            let n = stream.read(&mut buf).unwrap_or(0);
            stream.write_all(&buf[..n]).ok();
        },
        |port| {
            let src = format!(
                r#"
                s = Socket.connect("127.0.0.1", {port})
                s.write("hello")
                s.read_all()
                "#
            );
            assert_eq!(eval(&src), VmValue::Str("hello".to_string()));
        },
    );
}

#[test]
fn read_line_strips_crlf() {
    with_server(
        |mut stream| {
            stream.write_all(b"HTTP/1.0 200 OK\r\n").ok();
        },
        |port| {
            let src = format!(
                r#"Socket.connect("127.0.0.1", {port}).read_line()"#
            );
            assert_eq!(eval(&src), VmValue::Str("HTTP/1.0 200 OK".to_string()));
        },
    );
}

#[test]
fn read_bytes_exact() {
    with_server(
        |mut stream| {
            stream.write_all(b"Hello, World!").ok();
        },
        |port| {
            let src = format!(
                r#"
                s = Socket.connect("127.0.0.1", {port})
                s.read_bytes(5)
                "#
            );
            assert_eq!(eval(&src), VmValue::Str("Hello".to_string()));
        },
    );
}

#[test]
fn close_removes_fd() {
    with_server(
        |mut stream| {
            stream.write_all(b"hi").ok();
        },
        |port| {
            let src = format!(
                r#"
                s = Socket.connect("127.0.0.1", {port})
                s.close()
                s.read_all()
                "#
            );
            assert!(matches!(eval_err(&src), VmError::Raised(_)));
        },
    );
}

#[test]
fn bad_host_raises() {
    let src = r#"Socket.connect("this.host.does.not.exist.invalid", 80)"#;
    assert!(matches!(eval_err(src), VmError::Raised(_)));
}