add sample script as integration test (#559)
* updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
2
src/wasm-lib/Cargo.lock
generated
2
src/wasm-lib/Cargo.lock
generated
@ -1310,7 +1310,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.26"
|
||||
version = "0.1.28"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language"
|
||||
version = "0.1.26"
|
||||
version = "0.1.28"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -510,8 +510,6 @@ impl BinaryPart {
|
||||
let mut new_pipe_info = pipe_info.clone();
|
||||
new_pipe_info.is_in_pipe = false;
|
||||
|
||||
println!("BinaryPart::get_result: {:?}", self);
|
||||
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => Ok(literal.into()),
|
||||
BinaryPart::Identifier(identifier) => {
|
||||
|
@ -4,6 +4,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
@ -14,8 +15,7 @@ use crate::errors::{KclError, KclErrorDetails};
|
||||
pub struct EngineConnection {
|
||||
tcp_write: futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>,
|
||||
tcp_read_handle: tokio::task::JoinHandle<Result<()>>,
|
||||
export_notifier: Arc<tokio::sync::Notify>,
|
||||
snapshot_notifier: Arc<tokio::sync::Notify>,
|
||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||
}
|
||||
|
||||
impl Drop for EngineConnection {
|
||||
@ -31,8 +31,10 @@ pub struct TcpRead {
|
||||
|
||||
impl TcpRead {
|
||||
pub async fn read(&mut self) -> Result<WebSocketResponse> {
|
||||
let msg = self.stream.next().await.unwrap()?;
|
||||
let msg: WebSocketResponse = match msg {
|
||||
let Some(msg) = self.stream.next().await else {
|
||||
anyhow::bail!("Failed to read from websocket");
|
||||
};
|
||||
let msg: WebSocketResponse = match msg? {
|
||||
WsMsg::Text(text) => serde_json::from_str(&text)?,
|
||||
WsMsg::Binary(bin) => bson::from_slice(&bin)?,
|
||||
other => anyhow::bail!("Unexpected websocket message from server: {}", other),
|
||||
@ -42,17 +44,7 @@ impl TcpRead {
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(ws: reqwest::Upgraded, export_dir: &str, snapshot_file: &str) -> Result<EngineConnection> {
|
||||
// Make sure the export directory exists and that it is a directory.
|
||||
let export_dir = std::path::Path::new(export_dir).to_owned();
|
||||
if !export_dir.exists() {
|
||||
anyhow::bail!("Export directory does not exist: {}", export_dir.display());
|
||||
}
|
||||
// Make sure it is a directory.
|
||||
if !export_dir.is_dir() {
|
||||
anyhow::bail!("Export directory is not a directory: {}", export_dir.display());
|
||||
}
|
||||
|
||||
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
|
||||
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
ws,
|
||||
tokio_tungstenite::tungstenite::protocol::Role::Client,
|
||||
@ -64,72 +56,20 @@ impl EngineConnection {
|
||||
|
||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
||||
|
||||
let export_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let export_notifier_clone = export_notifier.clone();
|
||||
|
||||
let snapshot_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let snapshot_notifier_clone = snapshot_notifier.clone();
|
||||
|
||||
let snapshot_file = snapshot_file.to_owned();
|
||||
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
||||
let responses_clone = responses.clone();
|
||||
|
||||
let tcp_read_handle = tokio::spawn(async move {
|
||||
// Get Websocket messages from API server
|
||||
loop {
|
||||
match tcp_read.read().await {
|
||||
Ok(ws_resp) => {
|
||||
if let Some(success) = ws_resp.success {
|
||||
if !success {
|
||||
println!("got ws errors: {:?}", ws_resp.errors);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(msg) = ws_resp.resp {
|
||||
match msg {
|
||||
OkWebSocketResponseData::MetricsRequest {} => {
|
||||
// @paultag todo
|
||||
}
|
||||
OkWebSocketResponseData::IceServerInfo { ice_servers } => {
|
||||
println!("got ice server info: {:?}", ice_servers);
|
||||
}
|
||||
OkWebSocketResponseData::SdpAnswer { answer } => {
|
||||
println!("got sdp answer: {:?}", answer);
|
||||
}
|
||||
OkWebSocketResponseData::TrickleIce { candidate } => {
|
||||
println!("got trickle ice: {:?}", candidate);
|
||||
}
|
||||
OkWebSocketResponseData::Modeling { modeling_response } => {
|
||||
if let kittycad::types::OkModelingCmdResponse::TakeSnapshot { data } =
|
||||
modeling_response
|
||||
{
|
||||
if snapshot_file.is_empty() {
|
||||
println!("Got snapshot, but no snapshot file specified.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the snapshot locally.
|
||||
std::fs::write(&snapshot_file, data.contents)?;
|
||||
snapshot_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
OkWebSocketResponseData::Export { files } => {
|
||||
// Save the files to our export directory.
|
||||
for file in files {
|
||||
let path = export_dir.join(file.name);
|
||||
std::fs::write(&path, file.contents)?;
|
||||
println!("Wrote file: {}", path.display());
|
||||
}
|
||||
|
||||
// Tell the export notifier that we have new files.
|
||||
export_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
if let Some(id) = ws_resp.request_id {
|
||||
responses_clone.insert(id, ws_resp.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("got ws error: {:?}", e);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -139,19 +79,10 @@ impl EngineConnection {
|
||||
Ok(EngineConnection {
|
||||
tcp_write,
|
||||
tcp_read_handle,
|
||||
export_notifier: export_notifier_clone,
|
||||
snapshot_notifier: snapshot_notifier_clone,
|
||||
responses,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn wait_for_export(&self) {
|
||||
self.export_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_snapshot(&self) {
|
||||
self.snapshot_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> {
|
||||
let msg = serde_json::to_string(&msg)?;
|
||||
self.tcp_write.send(WsMsg::Text(msg)).await?;
|
||||
@ -159,6 +90,8 @@ impl EngineConnection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a modeling command.
|
||||
/// Do not wait for the response message.
|
||||
pub fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
@ -175,4 +108,28 @@ impl EngineConnection {
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a modeling command and wait for the response message.
|
||||
pub fn send_modeling_cmd_get_response(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
self.send_modeling_cmd(id, source_range, cmd)?;
|
||||
|
||||
// Wait for the response.
|
||||
loop {
|
||||
if let Some(resp) = self.responses.get(&id) {
|
||||
if let Some(data) = &resp.resp {
|
||||
return Ok(data.clone());
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Modeling command failed: {:?}", resp.errors),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,16 +34,11 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||
let mut engine = kcl_lib::engine::EngineConnection::new(
|
||||
ws,
|
||||
std::env::temp_dir().display().to_string().as_str(),
|
||||
output_file.display().to_string().as_str(),
|
||||
)
|
||||
.await?;
|
||||
let mut engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
||||
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
|
||||
|
||||
// Send a snapshot request to the engine.
|
||||
engine.send_modeling_cmd(
|
||||
let resp = engine.send_modeling_cmd_get_response(
|
||||
uuid::Uuid::new_v4(),
|
||||
kcl_lib::executor::SourceRange::default(),
|
||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
||||
@ -51,8 +46,15 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
||||
},
|
||||
)?;
|
||||
|
||||
// Wait for the snapshot to be taken.
|
||||
engine.wait_for_snapshot().await;
|
||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
||||
} = &resp
|
||||
{
|
||||
// Save the snapshot locally.
|
||||
std::fs::write(&output_file, &data.contents.0)?;
|
||||
} else {
|
||||
anyhow::bail!("Unexpected response from engine: {:?}", resp);
|
||||
}
|
||||
|
||||
// Read the output file.
|
||||
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
|
||||
@ -96,3 +98,29 @@ show(part001)"#;
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 1.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_parametric_example() {
|
||||
let code = r#"const sigmaAllow = 35000 // psi
|
||||
const width = 9 // inch
|
||||
const p = 150 // Force on shelf - lbs
|
||||
const distance = 6 // inches
|
||||
const FOS = 2
|
||||
|
||||
const leg1 = 5 // inches
|
||||
const leg2 = 8 // inches
|
||||
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
||||
const bracket = startSketchAt([0, 0])
|
||||
|> line([0, leg1], %)
|
||||
|> line([leg2, 0], %)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-leg2 + thickness, 0], %)
|
||||
|> line([0, -leg1 + thickness], %)
|
||||
|> close(%)
|
||||
|> extrude(width, %)
|
||||
|
||||
show(bracket)"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 1.0);
|
||||
}
|
||||
|
BIN
src/wasm-lib/tests/executor/outputs/parametric.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/parametric.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
Reference in New Issue
Block a user