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:
Jess Frazelle
2023-09-15 20:45:28 -07:00
committed by GitHub
parent c5d8779af4
commit 31eca3728e
6 changed files with 77 additions and 94 deletions

View File

@ -1310,7 +1310,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.26"
version = "0.1.28"
dependencies = [
"anyhow",
"bson",

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language"
version = "0.1.26"
version = "0.1.28"
edition = "2021"
license = "MIT"

View File

@ -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) => {

View File

@ -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],
}));
}
}
}
}
}

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB