Skip to content

Draft integration tests for Ark #542

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

Merged
merged 36 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6ef1332
Rename `Frontend` to `DummyFrontend`
lionel- Sep 23, 2024
6ecf5a9
Export `DummyFrontend` from Amalthea
lionel- Sep 23, 2024
843f7cc
Rename `shell_tx` to `iopub_tx`
lionel- Sep 23, 2024
7392c20
Export `start_kernel()` for tests
lionel- Sep 23, 2024
8457652
Add scaffholding for protocol tests
lionel- Sep 23, 2024
001850f
Fix R arguments
lionel- Sep 23, 2024
7210e8a
Implement suicide hook
lionel- Sep 23, 2024
8500112
Wrap dummy Ark frontend in singleton
lionel- Sep 24, 2024
4441514
Export `DummyArkFrontend` from Ark
lionel- Sep 24, 2024
45478e4
Add `send_execute_request()` method
lionel- Sep 24, 2024
788b0ca
Add more wrapper methods
lionel- Sep 24, 2024
7f024e9
Rename `receive_` prefix to `recv_`
lionel- Sep 24, 2024
e5c2ea4
Rename `test/test.rs` to `test/utils.rs`
lionel- Sep 25, 2024
0288a3b
Don't set `R_HOME` in kernel spec
lionel- Sep 25, 2024
3f469a8
Set `RUST_LOG` in kernel spec
lionel- Sep 25, 2024
6c6866f
Move `start_r()` to `RMain::start()`
lionel- Sep 25, 2024
13fc7e6
Add `RMain::wait_r_initialized()`
lionel- Sep 25, 2024
c7a4adc
Set unlimited stack size in unit tests
lionel- Sep 25, 2024
242359d
Enable test-only features for integration tests
lionel- Sep 25, 2024
51fdd2d
Set stack size after initializing R
lionel- Sep 25, 2024
beb5ff1
Set `Suicide` on Windows
lionel- Sep 25, 2024
ba22890
Use `IS_TESTING` in `modules::initialize()`
lionel- Sep 25, 2024
b1847e9
Add `RMain::wait_initialized()`
lionel- Sep 26, 2024
ff4ef91
Provide debug information about incoming data
lionel- Sep 26, 2024
1353cf8
Comment on use of `once_cell::Lazy`
lionel- Sep 26, 2024
4945eba
Use `OnceLock` instead of `once_cell::Lazy`
lionel- Sep 26, 2024
3229efb
Move test fixtures to `src/fixtures`
lionel- Sep 27, 2024
a50b20e
Reuse socket receivers
lionel- Sep 27, 2024
dd5240a
Fix typo in `.complete_initialization()`
lionel- Sep 27, 2024
0e927ee
Include review suggestions
lionel- Sep 27, 2024
a62db57
Remove `wait_initialized()`
lionel- Sep 27, 2024
755a0e9
Use `IS_TESTING` instead of `RMain::is_initialized()`
lionel- Sep 27, 2024
47af0ad
Use `RMain::is_initialized()` after all
lionel- Sep 27, 2024
fafbd8c
Panic if `RMain::start()` is called more than once
lionel- Sep 27, 2024
1f5753d
Remove `RMain::initializing` flag
lionel- Sep 27, 2024
5af6ba6
Document reliance on resolver's version 2
lionel- Sep 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

# Was necessary after switching to dev tree-sitter to fix this warning:
# > some crates are on edition 2021 which defaults to `resolver = "2"`, but
# > virtual workspaces default to `resolver = "1"`
# > virtual workspaces default to `resolver = "1"`.
#
# Also necessary to enable the `testing` feature of harp only when testing
# (i.e. when building downstream packages like Ark with Harp's `testing`
# feature set in `dev-dependencies`).
resolver = "2"

members = [
Expand Down
4 changes: 2 additions & 2 deletions crates/amalthea/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ hex = "0.4.3"
hmac = "0.12.1"
log = "0.4.17"
nix = "0.26.2"
portpicker = "0.1.1"
rand = "0.8.5"
serde = { version = "1.0.154", features = ["derive"] }
serde_json = { version = "1.0.94", features = ["preserve_order"]}
sha2 = "0.10.6"
Expand All @@ -34,6 +36,4 @@ serde_repr = "0.1.17"
tracing = "0.1.40"

[dev-dependencies]
rand = "0.8.5"
portpicker = "0.1.1"
env_logger = "0.10.0"
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
/*
* mod.rs
* dummy_frontend.rs
*
* Copyright (C) 2022 Posit Software, PBC. All rights reserved.
* Copyright (C) 2022-2024 Posit Software, PBC. All rights reserved.
*
*/

use amalthea::connection_file::ConnectionFile;
use amalthea::session::Session;
use amalthea::socket::socket::Socket;
use amalthea::wire::jupyter_message::JupyterMessage;
use amalthea::wire::jupyter_message::Message;
use amalthea::wire::jupyter_message::ProtocolMessage;
use serde_json::Value;
use stdext::assert_match;

pub struct Frontend {
use crate::connection_file::ConnectionFile;
use crate::session::Session;
use crate::socket::socket::Socket;
use crate::wire::execute_input::ExecuteInput;
use crate::wire::execute_reply::ExecuteReply;
use crate::wire::execute_request::ExecuteRequest;
use crate::wire::jupyter_message::JupyterMessage;
use crate::wire::jupyter_message::Message;
use crate::wire::jupyter_message::ProtocolMessage;
use crate::wire::status::ExecutionState;
use crate::wire::wire_message::WireMessage;

pub struct DummyFrontend {
pub _control_socket: Socket,
pub shell_socket: Socket,
pub iopub_socket: Socket,
Expand All @@ -27,7 +35,7 @@ pub struct Frontend {
heartbeat_port: u16,
}

impl Frontend {
impl DummyFrontend {
pub fn new() -> Self {
use rand::Rng;

Expand Down Expand Up @@ -117,7 +125,7 @@ impl Frontend {

/// Completes initialization of the frontend (usually done after the kernel
/// is ready and connected)
pub fn complete_intialization(&self) {
pub fn complete_initialization(&self) {
self.iopub_socket.subscribe().unwrap();
}

Expand All @@ -130,29 +138,100 @@ impl Frontend {
id
}

pub fn send_execute_request(&self, code: &str) -> String {
self.send_shell(ExecuteRequest {
code: String::from(code),
silent: false,
store_history: true,
user_expressions: serde_json::Value::Null,
allow_stdin: false,
stop_on_error: false,
})
}

/// Sends a Jupyter message on the Stdin socket
pub fn send_stdin<T: ProtocolMessage>(&self, msg: T) {
let message = JupyterMessage::create(msg, None, &self.session);
message.send(&self.stdin_socket).unwrap();
}

/// Receives a Jupyter message from the Shell socket
pub fn receive_shell(&self) -> Message {
pub fn recv_shell(&self) -> Message {
Message::read_from_socket(&self.shell_socket).unwrap()
}

/// Receive from Shell and assert ExecuteReply message
pub fn recv_shell_execute_reply(&self) -> ExecuteReply {
let msg = self.recv_shell();

assert_match!(msg, Message::ExecuteReply(data) => {
data.content
})
}

/// Receives a Jupyter message from the IOPub socket
pub fn receive_iopub(&self) -> Message {
pub fn recv_iopub(&self) -> Message {
Message::read_from_socket(&self.iopub_socket).unwrap()
}

/// Receive from IOPub and assert Busy message
pub fn recv_iopub_busy(&self) -> () {
let msg = self.recv_iopub();

assert_match!(msg, Message::Status(data) => {
assert_eq!(data.content.execution_state, ExecutionState::Busy);
});
}

/// Receive from IOPub and assert Idle message
pub fn recv_iopub_idle(&self) -> () {
let msg = self.recv_iopub();

assert_match!(msg, Message::Status(data) => {
assert_eq!(data.content.execution_state, ExecutionState::Idle);
});
}

/// Receive from IOPub and assert ExecuteInput message
pub fn recv_iopub_execute_input(&self) -> ExecuteInput {
let msg = self.recv_iopub();

assert_match!(msg, Message::ExecuteInput(data) => {
data.content
})
}

/// Receive from IOPub and assert ExecuteResult message. Returns compulsory
/// `plain/text` result.
pub fn recv_iopub_execute_result(&self) -> String {
let msg = self.recv_iopub();

assert_match!(msg, Message::ExecuteResult(data) => {
assert_match!(data.content.data, Value::Object(map) => {
assert_match!(map["text/plain"], Value::String(ref string) => {
string.clone()
})
})
})
}

/// Receive from IOPub and assert ExecuteResult message. Returns compulsory
/// `evalue` field.
pub fn recv_iopub_execute_error(&self) -> String {
let msg = self.recv_iopub();

assert_match!(msg, Message::ExecuteError(data) => {
data.content.exception.evalue
})
}

/// Receives a Jupyter message from the Stdin socket
pub fn receive_stdin(&self) -> Message {
pub fn recv_stdin(&self) -> Message {
Message::read_from_socket(&self.stdin_socket).unwrap()
}

/// Receives a (raw) message from the heartbeat socket
pub fn receive_heartbeat(&self) -> zmq::Message {
pub fn recv_heartbeat(&self) -> zmq::Message {
let mut msg = zmq::Message::new();
self.heartbeat_socket.recv(&mut msg).unwrap();
msg
Expand All @@ -178,4 +257,39 @@ impl Frontend {
key: self.key.clone(),
}
}

/// Asserts that no socket has incoming data
pub fn assert_no_incoming(&mut self) {
let mut has_incoming = false;

if self.iopub_socket.has_incoming_data().unwrap() {
has_incoming = true;
Self::flush_incoming("IOPub", &self.iopub_socket);
}
if self.shell_socket.has_incoming_data().unwrap() {
has_incoming = true;
Self::flush_incoming("Shell", &self.shell_socket);
}
if self.stdin_socket.has_incoming_data().unwrap() {
has_incoming = true;
Self::flush_incoming("StdIn", &self.stdin_socket);
}
if self.heartbeat_socket.has_incoming_data().unwrap() {
has_incoming = true;
Self::flush_incoming("Heartbeat", &self.heartbeat_socket);
}

if has_incoming {
panic!("Sockets must be empty on exit (see details above)");
}
}

fn flush_incoming(name: &str, socket: &Socket) {
println!("{name} has incoming data:");

while socket.has_incoming_data().unwrap() {
dbg!(WireMessage::read_from_socket(socket).unwrap());
println!("---");
}
}
}
1 change: 1 addition & 0 deletions crates/amalthea/src/fixtures/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod dummy_frontend;
1 change: 1 addition & 0 deletions crates/amalthea/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
pub mod comm;
pub mod connection_file;
pub mod error;
pub mod fixtures;
pub mod kernel;
pub mod kernel_dirs;
pub mod kernel_spec;
Expand Down
4 changes: 4 additions & 0 deletions crates/amalthea/src/socket/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ impl Socket {
}
}

pub fn has_incoming_data(&self) -> zmq::Result<bool> {
Ok(self.socket.poll(zmq::PollEvents::POLLIN, 0)? != 0)
}

/// Subscribes a SUB socket to all the published messages from a PUB socket.
///
/// Note that this needs to be called *after* the socket connection is
Expand Down
Loading
Loading