Compare commits
	
		
			12 Commits
		
	
	
		
			pierremtb/
			...
			achalmers/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f297e0827e | |||
| 70023a31ef | |||
| fb0def6797 | |||
| 793e3cfa95 | |||
| 7f25c4ebed | |||
| e66204398f | |||
| 255dbc70da | |||
| 6e93375f26 | |||
| 28b30127cb | |||
| 8064ac8b31 | |||
| c3040aa053 | |||
| c12b5b67ee | 
							
								
								
									
										14
									
								
								.github/workflows/cargo-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -59,11 +59,21 @@ jobs: | ||||
|       - uses: taiki-e/install-action@nextest | ||||
|       - name: Rust Cache | ||||
|         uses: Swatinem/rust-cache@v2.6.1 | ||||
|       - name: cargo test | ||||
|       - name: Compile tests | ||||
|         run: |- | ||||
|           cd "${{ matrix.dir }}" | ||||
|           cargo nextest archive --archive-file tests.tar.zst --workspace --profile ci | ||||
|       - name: Start test KCL server | ||||
|         run: |- | ||||
|           cd "${{ matrix.dir }}" | ||||
|           cargo build --quiet --bin kcl-test-server --workspace && ./target/debug/kcl-test-server & | ||||
|         env: | ||||
|           KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} | ||||
|       - name: Run tests | ||||
|         shell: bash | ||||
|         run: |- | ||||
|           cd "${{ matrix.dir }}" | ||||
|           cargo llvm-cov nextest --all --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log | ||||
|           cargo llvm-cov nextest --lcov --output-path lcov.info --test-threads=1 --no-fail-fast --profile ci --archive-file tests.tar.zst 2>&1 | tee /tmp/github-actions.log | ||||
|         env: | ||||
|           KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} | ||||
|           RUST_MIN_STACK: 10485760000 | ||||
|  | ||||
| @ -1,3 +1,8 @@ | ||||
| # experimental = ["setup-scripts"] | ||||
|  | ||||
| # [script.test-server] | ||||
| # command = "just start-test-server" | ||||
|  | ||||
| # Each test can have at most 4 threads, but if its name contains "serial_test_", then it | ||||
| # also requires 4 threads. | ||||
| # This means such tests run one at a time, with 4 threads. | ||||
| @ -14,12 +19,20 @@ slow-timeout = { period = "50s", terminate-after = 5 } | ||||
| [[profile.default.overrides]] | ||||
| filter = "test(serial_test_)" | ||||
| test-group = "serial-integration" | ||||
| threads-required = 2 | ||||
| threads-required = 4 | ||||
|  | ||||
| # [[profile.default.scripts]] | ||||
| # filter = 'test(serial_test_)' | ||||
| # setup = 'test-server' | ||||
|  | ||||
| [[profile.ci.overrides]] | ||||
| filter = "test(serial_test_)" | ||||
| test-group = "serial-integration" | ||||
| threads-required = 2 | ||||
| threads-required = 4 | ||||
|  | ||||
| # [[profile.default.scripts]] | ||||
| # filter = 'test(serial_test_)' | ||||
| # setup = 'test-server' | ||||
|  | ||||
| [[profile.default.overrides]] | ||||
| filter = "test(parser::parser_impl::snapshot_tests)" | ||||
|  | ||||
							
								
								
									
										23
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -1434,6 +1434,19 @@ dependencies = [ | ||||
|  "syn 2.0.66", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-test-server" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "hyper", | ||||
|  "kcl-lib", | ||||
|  "pico-args", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "tokio", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kittycad" | ||||
| version = "0.3.5" | ||||
| @ -1815,6 +1828,12 @@ dependencies = [ | ||||
|  "thiserror", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pico-args" | ||||
| version = "0.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" | ||||
|  | ||||
| [[package]] | ||||
| name = "pin-project" | ||||
| version = "1.1.5" | ||||
| @ -2492,9 +2511,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_json" | ||||
| version = "1.0.116" | ||||
| version = "1.0.117" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" | ||||
| checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" | ||||
| dependencies = [ | ||||
|  "indexmap 2.2.5", | ||||
|  "itoa", | ||||
|  | ||||
| @ -17,7 +17,7 @@ kcl-lib = { path = "kcl" } | ||||
| kittycad.workspace = true | ||||
| serde_json = "1.0.116" | ||||
| tokio = { version = "1.38.0", features = ["sync"] } | ||||
| toml = "0.8.14" | ||||
| toml = "0.8.13" | ||||
| uuid = { version = "1.8.0", features = ["v4", "js", "serde"] } | ||||
| wasm-bindgen = "0.2.91" | ||||
| wasm-bindgen-futures = "0.4.42" | ||||
| @ -65,6 +65,7 @@ members = [ | ||||
| 	"derive-docs", | ||||
| 	"kcl", | ||||
| 	"kcl-macros", | ||||
| 	"kcl-test-server", | ||||
| ] | ||||
|  | ||||
| [workspace.dependencies] | ||||
|  | ||||
							
								
								
									
										2
									
								
								src/wasm-lib/justfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| start-test-server: | ||||
|     cargo build --quiet --bin kcl-test-server --workspace && ./target/debug/kcl-test-server | ||||
							
								
								
									
										13
									
								
								src/wasm-lib/kcl-test-server/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| [package] | ||||
| name = "kcl-test-server" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| anyhow = "1.0.86" | ||||
| hyper = { version = "0.14.29", features = ["server"] } | ||||
| kcl-lib = { path = "../kcl" } | ||||
| pico-args = "0.5.0" | ||||
| serde = { version = "1.0.203", features = ["derive"] } | ||||
| serde_json = "1.0.117" | ||||
| tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } | ||||
							
								
								
									
										210
									
								
								src/wasm-lib/kcl-test-server/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,210 @@ | ||||
| //! Executes KCL programs. | ||||
| //! The server reuses the same engine session for each KCL program it receives. | ||||
| use std::net::SocketAddr; | ||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | ||||
| use std::sync::Arc; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use hyper::body::Bytes; | ||||
| use hyper::header::CONTENT_TYPE; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Error, Response, Server}; | ||||
| use kcl_lib::executor::ExecutorContext; | ||||
| use kcl_lib::settings::types::UnitLength; | ||||
| use kcl_lib::test_server::RequestBody; | ||||
| use tokio::sync::{mpsc, oneshot}; | ||||
| use tokio::task::JoinHandle; | ||||
| use tokio::time::sleep; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> anyhow::Result<()> { | ||||
|     // Parse the CLI arguments. | ||||
|     let pargs = pico_args::Arguments::from_env(); | ||||
|     let args = ServerArgs::parse(pargs)?; | ||||
|     // Run the actual server. | ||||
|     start_server(args).await | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct ServerArgs { | ||||
|     /// What port this server should listen on. | ||||
|     listen_on: SocketAddr, | ||||
|     /// How many connections to establish with the engine. | ||||
|     num_engine_conns: u8, | ||||
| } | ||||
|  | ||||
| impl ServerArgs { | ||||
|     fn parse(mut pargs: pico_args::Arguments) -> Result<Self, pico_args::Error> { | ||||
|         let args = ServerArgs { | ||||
|             listen_on: pargs | ||||
|                 .opt_value_from_str("--listen-on")? | ||||
|                 .unwrap_or("0.0.0.0:3333".parse().unwrap()), | ||||
|             num_engine_conns: pargs.opt_value_from_str("--num-engine-conns")?.unwrap_or(1), | ||||
|         }; | ||||
|         println!("Config is {args:?}"); | ||||
|         Ok(args) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Sent from the server to each worker. | ||||
| struct WorkerReq { | ||||
|     /// A KCL program, in UTF-8. | ||||
|     body: Bytes, | ||||
|     /// A channel to send the HTTP response back. | ||||
|     resp: oneshot::Sender<Response<Body>>, | ||||
| } | ||||
|  | ||||
| /// Each worker has a connection to the engine, and accepts | ||||
| /// KCL programs. When it receives one (over the mpsc channel) | ||||
| /// it executes it and returns the result via a oneshot channel. | ||||
| fn start_worker(i: u8) -> mpsc::Sender<WorkerReq> { | ||||
|     println!("Starting worker {i}"); | ||||
|     // Make a work queue for this worker. | ||||
|     let (tx, mut rx) = mpsc::channel(1); | ||||
|     tokio::task::spawn(async move { | ||||
|         let state = ExecutorContext::new_for_unit_test(UnitLength::Mm).await.unwrap(); | ||||
|         println!("Worker {i} ready"); | ||||
|         while let Some(req) = rx.recv().await { | ||||
|             let req: WorkerReq = req; | ||||
|             let resp = snapshot_endpoint(req.body, state.clone()).await; | ||||
|             if req.resp.send(resp).is_err() { | ||||
|                 println!("\tWorker {i} exiting"); | ||||
|             } | ||||
|         } | ||||
|         println!("\tWorker {i} exiting"); | ||||
|     }); | ||||
|     tx | ||||
| } | ||||
|  | ||||
| struct ServerState { | ||||
|     workers: Vec<mpsc::Sender<WorkerReq>>, | ||||
|     req_num: AtomicUsize, | ||||
| } | ||||
|  | ||||
| async fn start_server(args: ServerArgs) -> anyhow::Result<()> { | ||||
|     let ServerArgs { | ||||
|         listen_on, | ||||
|         num_engine_conns, | ||||
|     } = args; | ||||
|     let workers: Vec<_> = (0..num_engine_conns).map(start_worker).collect(); | ||||
|     let state = Arc::new(ServerState { | ||||
|         workers, | ||||
|         req_num: 0.into(), | ||||
|     }); | ||||
|     // In hyper, a `MakeService` is basically your server. | ||||
|     // It makes a `Service` for each connection, which manages the connection. | ||||
|     let make_service = make_service_fn( | ||||
|         // This closure is run for each connection. | ||||
|         move |_conn_info| { | ||||
|             // eprintln!("Connected to a client"); | ||||
|             let state = state.clone(); | ||||
|             async move { | ||||
|                 // This is the `Service` which handles the connection. | ||||
|                 // `service_fn` converts a function which returns a Response | ||||
|                 // into a `Service`. | ||||
|                 Ok::<_, Error>(service_fn(move |req| { | ||||
|                     // eprintln!("Received a request"); | ||||
|                     let state = state.clone(); | ||||
|                     async move { handle_request(req, state).await } | ||||
|                 })) | ||||
|             } | ||||
|         }, | ||||
|     ); | ||||
|     let server = Server::bind(&listen_on).serve(make_service); | ||||
|     println!("Listening on {listen_on}"); | ||||
|     println!("PID is {}", std::process::id()); | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("Server error: {e}"); | ||||
|         return Err(e.into()); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| async fn handle_request(req: hyper::Request<Body>, state3: Arc<ServerState>) -> Result<Response<Body>, Error> { | ||||
|     let body = hyper::body::to_bytes(req.into_body()).await?; | ||||
|  | ||||
|     // Round robin requests between each available worker. | ||||
|     let req_num = state3.req_num.fetch_add(1, Ordering::Relaxed); | ||||
|     let worker_id = req_num % state3.workers.len(); | ||||
|     // println!("Sending request {req_num} to worker {worker_id}"); | ||||
|     let worker = state3.workers[worker_id].clone(); | ||||
|     let (tx, rx) = oneshot::channel(); | ||||
|     let req_sent = worker.send(WorkerReq { body, resp: tx }).await; | ||||
|     req_sent.unwrap(); | ||||
|     let resp = rx.await.unwrap(); | ||||
|     Ok(resp) | ||||
| } | ||||
|  | ||||
| /// Execute a KCL program, then respond with a PNG snapshot. | ||||
| /// KCL errors (from engine or the executor) respond with HTTP Bad Gateway. | ||||
| /// Malformed requests are HTTP Bad Request. | ||||
| /// Successful requests contain a PNG as the body. | ||||
| async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body> { | ||||
|     let body = match serde_json::from_slice::<RequestBody>(body.as_ref()) { | ||||
|         Ok(bd) => bd, | ||||
|         Err(e) => return bad_request(format!("Invalid request JSON: {e}")), | ||||
|     }; | ||||
|     let RequestBody { kcl_program, test_name } = body; | ||||
|     let parser = match kcl_lib::token::lexer(&kcl_program) { | ||||
|         Ok(ts) => kcl_lib::parser::Parser::new(ts), | ||||
|         Err(e) => return bad_request(format!("tokenization error: {e}")), | ||||
|     }; | ||||
|     let program = match parser.ast() { | ||||
|         Ok(pr) => pr, | ||||
|         Err(e) => return bad_request(format!("Parse error: {e}")), | ||||
|     }; | ||||
|     eprintln!("Executing {test_name}"); | ||||
|     if let Err(e) = state.reset_scene().await { | ||||
|         return kcl_err(e); | ||||
|     } | ||||
|     // Let users know if the test is taking a long time. | ||||
|     let (done_tx, done_rx) = oneshot::channel::<()>(); | ||||
|     let timer = time_until(done_rx); | ||||
|     let snapshot = match state.execute_and_prepare_snapshot(program).await { | ||||
|         Ok(sn) => sn, | ||||
|         Err(e) => return kcl_err(e), | ||||
|     }; | ||||
|     let _ = done_tx.send(()); | ||||
|     timer.abort(); | ||||
|     eprintln!("\tServing response"); | ||||
|     let png_bytes = snapshot.contents.0; | ||||
|     let mut resp = Response::new(Body::from(png_bytes)); | ||||
|     resp.headers_mut().insert(CONTENT_TYPE, "image/png".parse().unwrap()); | ||||
|     resp | ||||
| } | ||||
|  | ||||
| fn bad_request(msg: String) -> Response<Body> { | ||||
|     eprintln!("\tBad request"); | ||||
|     let mut resp = Response::new(Body::from(msg)); | ||||
|     *resp.status_mut() = hyper::StatusCode::BAD_REQUEST; | ||||
|     resp | ||||
| } | ||||
|  | ||||
| fn bad_gateway(msg: String) -> Response<Body> { | ||||
|     eprintln!("\tBad gateway"); | ||||
|     let mut resp = Response::new(Body::from(msg)); | ||||
|     *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; | ||||
|     resp | ||||
| } | ||||
|  | ||||
| fn kcl_err(err: anyhow::Error) -> Response<Body> { | ||||
|     eprintln!("\tBad KCL"); | ||||
|     bad_gateway(format!("{err}")) | ||||
| } | ||||
|  | ||||
| fn time_until(done: oneshot::Receiver<()>) -> JoinHandle<()> { | ||||
|     tokio::task::spawn(async move { | ||||
|         let period = 10; | ||||
|         tokio::pin!(done); | ||||
|         for i in 1..=3 { | ||||
|             tokio::select! { | ||||
|                 biased; | ||||
|                 // If the test is done, no need for this timer anymore. | ||||
|                 _ = &mut done => return, | ||||
|                 _ = sleep(Duration::from_secs(period)) => { | ||||
|                     eprintln!("\tTest has taken {}s", period * i); | ||||
|                 }, | ||||
|             }; | ||||
|         } | ||||
|     }) | ||||
| } | ||||
| @ -40,23 +40,54 @@ pub struct TcpRead { | ||||
|     stream: futures::stream::SplitStream<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>>, | ||||
| } | ||||
|  | ||||
| /// Occurs when client couldn't read from the WebSocket to the engine. | ||||
| // #[derive(Debug)] | ||||
| pub enum WebSocketReadError { | ||||
|     /// Could not read a message due to WebSocket errors. | ||||
|     Read(tokio_tungstenite::tungstenite::Error), | ||||
|     /// WebSocket message didn't contain a valid message that the KCL Executor could parse. | ||||
|     Deser(anyhow::Error), | ||||
| } | ||||
|  | ||||
| impl From<anyhow::Error> for WebSocketReadError { | ||||
|     fn from(e: anyhow::Error) -> Self { | ||||
|         Self::Deser(e) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TcpRead { | ||||
|     pub async fn read(&mut self) -> Result<WebSocketResponse> { | ||||
|     pub async fn read(&mut self) -> std::result::Result<WebSocketResponse, WebSocketReadError> { | ||||
|         let Some(msg) = self.stream.next().await else { | ||||
|             anyhow::bail!("Failed to read from websocket"); | ||||
|             return Err(anyhow::anyhow!("Failed to read from WebSocket").into()); | ||||
|         }; | ||||
|         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), | ||||
|         let msg = match msg { | ||||
|             Ok(msg) => msg, | ||||
|             Err(e) if matches!(e, tokio_tungstenite::tungstenite::Error::Protocol(_)) => { | ||||
|                 return Err(WebSocketReadError::Read(e)) | ||||
|             } | ||||
|             Err(e) => return Err(anyhow::anyhow!("Error reading from engine's WebSocket: {e}").into()), | ||||
|         }; | ||||
|         let msg: WebSocketResponse = match msg { | ||||
|             WsMsg::Text(text) => serde_json::from_str(&text) | ||||
|                 .map_err(anyhow::Error::from) | ||||
|                 .map_err(WebSocketReadError::from)?, | ||||
|             WsMsg::Binary(bin) => bson::from_slice(&bin) | ||||
|                 .map_err(anyhow::Error::from) | ||||
|                 .map_err(WebSocketReadError::from)?, | ||||
|             other => return Err(anyhow::anyhow!("Unexpected WebSocket message from engine API: {other}").into()), | ||||
|         }; | ||||
|         Ok(msg) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct TcpReadHandle { | ||||
|     handle: Arc<tokio::task::JoinHandle<Result<()>>>, | ||||
|     handle: Arc<tokio::task::JoinHandle<Result<(), WebSocketReadError>>>, | ||||
| } | ||||
|  | ||||
| impl std::fmt::Debug for TcpReadHandle { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "TcpReadHandle") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Drop for TcpReadHandle { | ||||
| @ -150,14 +181,17 @@ impl EngineConnection { | ||||
|                 match tcp_read.read().await { | ||||
|                     Ok(ws_resp) => { | ||||
|                         for e in ws_resp.errors.iter().flatten() { | ||||
|                             println!("got error message: {e}"); | ||||
|                             println!("got error message: {} {}", e.error_code, e.message); | ||||
|                         } | ||||
|                         if let Some(id) = ws_resp.request_id { | ||||
|                             responses_clone.insert(id, ws_resp.clone()); | ||||
|                         } | ||||
|                     } | ||||
|                     Err(e) => { | ||||
|                         println!("got ws error: {:?}", e); | ||||
|                         match &e { | ||||
|                             WebSocketReadError::Read(e) => eprintln!("could not read from WS: {:?}", e), | ||||
|                             WebSocketReadError::Deser(e) => eprintln!("could not deserialize msg from WS: {:?}", e), | ||||
|                         } | ||||
|                         *socket_health_tcp_read.lock().unwrap() = SocketHealth::Inactive; | ||||
|                         return Err(e); | ||||
|                     } | ||||
|  | ||||
| @ -15,6 +15,7 @@ use crate::{ | ||||
|     engine::EngineManager, | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     fs::FileManager, | ||||
|     settings::types::UnitLength, | ||||
|     std::{FunctionKind, StdLib}, | ||||
| }; | ||||
|  | ||||
| @ -992,7 +993,7 @@ pub struct ExecutorContext { | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct ExecutorSettings { | ||||
|     /// The unit to use in modeling dimensions. | ||||
|     pub units: crate::settings::types::UnitLength, | ||||
|     pub units: UnitLength, | ||||
|     /// Highlight edges of 3D objects? | ||||
|     pub highlight_edges: bool, | ||||
|     /// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled. | ||||
| @ -1083,6 +1084,57 @@ impl ExecutorContext { | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// For executing unit tests. | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     pub async fn new_for_unit_test(units: UnitLength) -> Result<Self> { | ||||
|         let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),); | ||||
|         let http_client = reqwest::Client::builder() | ||||
|             .user_agent(user_agent) | ||||
|             // For file conversions we need this to be long. | ||||
|             .timeout(std::time::Duration::from_secs(600)) | ||||
|             .connect_timeout(std::time::Duration::from_secs(60)); | ||||
|         let ws_client = reqwest::Client::builder() | ||||
|             .user_agent(user_agent) | ||||
|             // For file conversions we need this to be long. | ||||
|             .timeout(std::time::Duration::from_secs(600)) | ||||
|             .connect_timeout(std::time::Duration::from_secs(60)) | ||||
|             .connection_verbose(true) | ||||
|             .tcp_keepalive(std::time::Duration::from_secs(600)) | ||||
|             .http1_only(); | ||||
|  | ||||
|         let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set"); | ||||
|  | ||||
|         // Create the client. | ||||
|         let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client); | ||||
|         // Set a local engine address if it's set. | ||||
|         if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") { | ||||
|             client.set_base_url(addr); | ||||
|         } | ||||
|  | ||||
|         let ctx = ExecutorContext::new( | ||||
|             &client, | ||||
|             ExecutorSettings { | ||||
|                 units, | ||||
|                 highlight_edges: true, | ||||
|                 enable_ssao: false, | ||||
|             }, | ||||
|         ) | ||||
|         .await?; | ||||
|         Ok(ctx) | ||||
|     } | ||||
|  | ||||
|     /// Clear everything in the scene. | ||||
|     pub async fn reset_scene(&self) -> Result<()> { | ||||
|         self.engine | ||||
|             .send_modeling_cmd( | ||||
|                 uuid::Uuid::new_v4(), | ||||
|                 SourceRange::default(), | ||||
|                 kittycad::types::ModelingCmd::SceneClearAll {}, | ||||
|             ) | ||||
|             .await?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Perform the execution of a program. | ||||
|     /// You can optionally pass in some initialization memory. | ||||
|     /// Kurt uses this for partial execution. | ||||
| @ -1309,7 +1361,7 @@ impl ExecutorContext { | ||||
|     } | ||||
|  | ||||
|     /// Update the units for the executor. | ||||
|     pub fn update_units(&mut self, units: crate::settings::types::UnitLength) { | ||||
|     pub fn update_units(&mut self, units: UnitLength) { | ||||
|         self.settings.units = units; | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -16,6 +16,7 @@ pub mod lsp; | ||||
| pub mod parser; | ||||
| pub mod settings; | ||||
| pub mod std; | ||||
| pub mod test_server; | ||||
| pub mod thread; | ||||
| pub mod token; | ||||
| #[cfg(target_arch = "wasm32")] | ||||
|  | ||||
							
								
								
									
										8
									
								
								src/wasm-lib/kcl/src/test_server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,8 @@ | ||||
| //! Types used to send data to the test server. | ||||
|  | ||||
| #[derive(serde::Deserialize, serde::Serialize)] | ||||
| pub struct RequestBody { | ||||
|     pub kcl_program: String, | ||||
|     #[serde(default)] | ||||
|     pub test_name: String, | ||||
| } | ||||
| @ -1,58 +1,39 @@ | ||||
| use std::io::Cursor; | ||||
|  | ||||
| use anyhow::Result; | ||||
| use kcl_lib::{ | ||||
|     executor::{ExecutorContext, ExecutorSettings}, | ||||
|     settings::types::UnitLength, | ||||
| }; | ||||
| use image::io::Reader as ImageReader; | ||||
| use kcl_lib::{executor::ExecutorContext, settings::types::UnitLength}; | ||||
| use kittycad::types::TakeSnapshot; | ||||
|  | ||||
| // mod server; | ||||
|  | ||||
| async fn new_context(units: UnitLength) -> Result<ExecutorContext> { | ||||
|     let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),); | ||||
|     let http_client = reqwest::Client::builder() | ||||
|         .user_agent(user_agent) | ||||
|         // For file conversions we need this to be long. | ||||
|         .timeout(std::time::Duration::from_secs(600)) | ||||
|         .connect_timeout(std::time::Duration::from_secs(60)); | ||||
|     let ws_client = reqwest::Client::builder() | ||||
|         .user_agent(user_agent) | ||||
|         // For file conversions we need this to be long. | ||||
|         .timeout(std::time::Duration::from_secs(600)) | ||||
|         .connect_timeout(std::time::Duration::from_secs(60)) | ||||
|         .connection_verbose(true) | ||||
|         .tcp_keepalive(std::time::Duration::from_secs(600)) | ||||
|         .http1_only(); | ||||
|  | ||||
|     let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set"); | ||||
|  | ||||
|     // Create the client. | ||||
|     let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client); | ||||
|     // Set a local engine address if it's set. | ||||
|     if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") { | ||||
|         client.set_base_url(addr); | ||||
|     } | ||||
|  | ||||
|     let ctx = ExecutorContext::new( | ||||
|         &client, | ||||
|         ExecutorSettings { | ||||
|             units, | ||||
|             highlight_edges: true, | ||||
|             enable_ssao: false, | ||||
|         }, | ||||
|     ) | ||||
|     .await?; | ||||
|     Ok(ctx) | ||||
| macro_rules! test_name { | ||||
|     () => {{ | ||||
|         fn f() {} | ||||
|         fn type_name_of<T>(_: T) -> &'static str { | ||||
|             std::any::type_name::<T>() | ||||
|         } | ||||
|         let name = type_name_of(f); | ||||
|         name.strip_suffix("::f") | ||||
|             .unwrap() | ||||
|             .strip_suffix("::{{closure}}") | ||||
|             .unwrap() | ||||
|             .strip_prefix("executor::serial_test_") | ||||
|             .unwrap() | ||||
|     }}; | ||||
| } | ||||
|  | ||||
| /// Executes a kcl program and takes a snapshot of the result. | ||||
| /// This returns the bytes of the snapshot. | ||||
| async fn execute_and_snapshot(code: &str, units: UnitLength) -> Result<image::DynamicImage> { | ||||
|     let ctx = new_context(units).await?; | ||||
|     let ctx = ExecutorContext::new_for_unit_test(units).await?; | ||||
|     let tokens = kcl_lib::token::lexer(code)?; | ||||
|     let parser = kcl_lib::parser::Parser::new(tokens); | ||||
|     let program = parser.ast()?; | ||||
|  | ||||
|     let snapshot = ctx.execute_and_prepare_snapshot(program).await?; | ||||
|     to_disk(snapshot) | ||||
| } | ||||
|  | ||||
| fn to_disk(snapshot: TakeSnapshot) -> Result<image::DynamicImage> { | ||||
|     // Create a temporary file to write the output to. | ||||
|     let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4())); | ||||
|     // Save the snapshot locally, to that temporary file. | ||||
| @ -81,28 +62,28 @@ const part002 = startSketchOn(part001, "here") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| #[tokio::test(flavor = "multi_thread")] | ||||
| async fn serial_test_riddle_small() { | ||||
|     let code = include_str!("inputs/riddle_small.kcl"); | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| #[tokio::test(flavor = "multi_thread")] | ||||
| async fn serial_test_lego() { | ||||
|     let code = include_str!("inputs/lego.kcl"); | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| #[tokio::test(flavor = "multi_thread")] | ||||
| async fn serial_test_pipe_as_arg() { | ||||
|     let code = include_str!("inputs/pipe_as_arg.kcl"); | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -137,14 +118,14 @@ const part002 = startSketchOn(part001, "start") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_start.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| #[tokio::test(flavor = "multi_thread")] | ||||
| async fn serial_test_mike_stress_lines() { | ||||
|     let code = include_str!("inputs/mike_stress_test.kcl"); | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/mike_stress_test.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -172,7 +153,7 @@ const part002 = startSketchOn(part001, "END") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_end.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -200,7 +181,7 @@ const part002 = startSketchOn(part001, "END") | ||||
|   |> extrude(-5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/sketch_on_face_end_negative_extrude.png", | ||||
|         &result, | ||||
| @ -220,7 +201,7 @@ async fn serial_test_fillet_duplicate_tags() { | ||||
|     |> fillet({radius: 0.5, tags: ["thing", "thing"]}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -240,7 +221,7 @@ async fn serial_test_basic_fillet_cube_start() { | ||||
|     |> fillet({radius: 2, tags: ["thing", "thing2"]}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_start.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -257,7 +238,7 @@ async fn serial_test_basic_fillet_cube_end() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_end.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -274,7 +255,7 @@ async fn serial_test_basic_fillet_cube_close_opposite() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/basic_fillet_cube_close_opposite.png", | ||||
|         &result, | ||||
| @ -294,7 +275,7 @@ async fn serial_test_basic_fillet_cube_next_adjacent() { | ||||
|     |> fillet({radius: 2, tags: [getNextAdjacentEdge("thing3", %)]}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/basic_fillet_cube_next_adjacent.png", | ||||
|         &result, | ||||
| @ -314,7 +295,7 @@ async fn serial_test_basic_fillet_cube_previous_adjacent() { | ||||
|     |> fillet({radius: 2, tags: [getPreviousAdjacentEdge("thing3", %)]}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/basic_fillet_cube_previous_adjacent.png", | ||||
|         &result, | ||||
| @ -339,7 +320,7 @@ async fn serial_test_execute_with_function_sketch() { | ||||
| const fnBox = box(3, 6, 10) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -359,7 +340,7 @@ async fn serial_test_execute_with_function_sketch_with_position() { | ||||
|  | ||||
| const thing = box([0,0], 3, 6, 10)"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/function_sketch_with_position.png", | ||||
|         &result, | ||||
| @ -380,7 +361,7 @@ async fn serial_test_execute_with_angled_line() { | ||||
|   |> extrude(4, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -406,7 +387,7 @@ const bracket = startSketchOn('XY') | ||||
|   |> extrude(width, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -440,7 +421,7 @@ const bracket = startSketchAt([0, 0]) | ||||
|   |> extrude(width, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -455,7 +436,7 @@ async fn serial_test_execute_engine_error_return() { | ||||
|   |> extrude(4, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -468,7 +449,7 @@ async fn serial_test_execute_i_shape() { | ||||
|     // This is some code from lee that starts a pipe expression with a variable. | ||||
|     let code = include_str!("inputs/i_shape.kcl"); | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/i_shape.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -477,7 +458,7 @@ async fn serial_test_execute_i_shape() { | ||||
| async fn serial_test_execute_pipes_on_pipes() { | ||||
|     let code = include_str!("inputs/pipes_on_pipes.kcl"); | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -485,7 +466,7 @@ async fn serial_test_execute_pipes_on_pipes() { | ||||
| async fn serial_test_execute_cylinder() { | ||||
|     let code = include_str!("inputs/cylinder.kcl"); | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/cylinder.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -493,7 +474,7 @@ async fn serial_test_execute_cylinder() { | ||||
| async fn serial_test_execute_kittycad_svg() { | ||||
|     let code = include_str!("inputs/kittycad_svg.kcl"); | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -518,7 +499,7 @@ const pt1 = b1.value[0] | ||||
| const pt2 = b2.value[0] | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/member_expression_sketch_group.png", | ||||
|         &result, | ||||
| @ -534,7 +515,7 @@ async fn serial_test_helix_defaults() { | ||||
|      |> helix({revolutions: 16, angle_start: 0}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/helix_defaults.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -546,7 +527,7 @@ async fn serial_test_helix_defaults_negative_extrude() { | ||||
|      |> helix({revolutions: 16, angle_start: 0}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/helix_defaults_negative_extrude.png", | ||||
|         &result, | ||||
| @ -562,7 +543,7 @@ async fn serial_test_helix_ccw() { | ||||
|      |> helix({revolutions: 16, angle_start: 0, ccw: true}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/helix_ccw.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -574,7 +555,7 @@ async fn serial_test_helix_with_length() { | ||||
|      |> helix({revolutions: 16, angle_start: 0, length: 3}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/helix_with_length.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -589,7 +570,7 @@ async fn serial_test_dimensions_match() { | ||||
|   |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/dimensions_match.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -606,7 +587,7 @@ const body = startSketchOn('XY') | ||||
|       |> extrude(height, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -632,7 +613,7 @@ box(10, 23, 8) | ||||
| let thing = box(-12, -15, 10) | ||||
| box(-20, -5, 10)"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -645,7 +626,7 @@ async fn serial_test_basic_tangential_arc() { | ||||
|     |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/tangential_arc.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -658,7 +639,7 @@ async fn serial_test_basic_tangential_arc_with_point() { | ||||
|     |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_with_point.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -671,7 +652,7 @@ async fn serial_test_basic_tangential_arc_to() { | ||||
|     |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_to.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -698,7 +679,7 @@ box(30, 43, 18, '-xy') | ||||
| let thing = box(-12, -15, 10, 'yz') | ||||
| box(-20, -5, 10, 'xy')"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/different_planes_same_drawing.png", | ||||
|         &result, | ||||
| @ -760,7 +741,7 @@ const part004 = startSketchOn('YZ') | ||||
|   |> close(%) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -777,7 +758,7 @@ async fn serial_test_holes() { | ||||
|   |> extrude(2, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -796,7 +777,7 @@ async fn optional_params() { | ||||
|  | ||||
| const thing = other_circle([2, 2], 20) | ||||
| "#; | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -832,7 +813,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4) | ||||
|   |> extrude(2, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/rounded_with_holes.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -840,7 +821,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4) | ||||
| async fn serial_test_top_level_expression() { | ||||
|     let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -854,7 +835,7 @@ const part =  startSketchOn('XY') | ||||
|     |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/patterns_linear_basic_with_math.png", | ||||
|         &result, | ||||
| @ -870,7 +851,7 @@ async fn serial_test_patterns_linear_basic() { | ||||
|     |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -886,7 +867,7 @@ async fn serial_test_patterns_linear_basic_3d() { | ||||
|     |> patternLinear3d({axis: [1, 0, 1], repetitions: 3, distance: 6}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -898,7 +879,7 @@ async fn serial_test_patterns_linear_basic_negative_distance() { | ||||
|     |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/patterns_linear_basic_negative_distance.png", | ||||
|         &result, | ||||
| @ -914,7 +895,7 @@ async fn serial_test_patterns_linear_basic_negative_axis() { | ||||
|     |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/patterns_linear_basic_negative_axis.png", | ||||
|         &result, | ||||
| @ -939,7 +920,7 @@ const rectangle = startSketchOn('XY') | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_holes.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -951,7 +932,7 @@ async fn serial_test_patterns_circular_basic_2d() { | ||||
|     |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_2d.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -967,7 +948,7 @@ async fn serial_test_patterns_circular_basic_3d() { | ||||
|     |> patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repetitions: 40, arcDegrees: 360, rotateDuplicates: false}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_3d.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -983,7 +964,7 @@ async fn serial_test_patterns_circular_3d_tilted_axis() { | ||||
|     |> patternCircular3d({axis: [1,1,0], center: [10, 0, 10], repetitions: 10, arcDegrees: 360, rotateDuplicates: true}, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/patterns_circular_3d_tilted_axis.png", | ||||
|         &result, | ||||
| @ -995,7 +976,7 @@ async fn serial_test_patterns_circular_3d_tilted_axis() { | ||||
| async fn serial_test_import_file_doesnt_exist() { | ||||
|     let code = r#"const model = import("thing.obj")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1007,7 +988,7 @@ async fn serial_test_import_file_doesnt_exist() { | ||||
| async fn serial_test_import_obj_with_mtl() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube.obj")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1015,7 +996,7 @@ async fn serial_test_import_obj_with_mtl() { | ||||
| async fn serial_test_import_obj_with_mtl_units() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube.obj", {type: "obj", units: "m"})"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl_units.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1023,7 +1004,7 @@ async fn serial_test_import_obj_with_mtl_units() { | ||||
| async fn serial_test_import_gltf_with_bin() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube.gltf")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1031,7 +1012,7 @@ async fn serial_test_import_gltf_with_bin() { | ||||
| async fn serial_test_import_gltf_embedded() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube-embedded.gltf")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1039,7 +1020,7 @@ async fn serial_test_import_gltf_embedded() { | ||||
| async fn serial_test_import_glb() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube.glb")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1047,7 +1028,7 @@ async fn serial_test_import_glb() { | ||||
| async fn serial_test_import_glb_no_assign() { | ||||
|     let code = r#"import("tests/executor/inputs/cube.glb")"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, 0.999); | ||||
| } | ||||
|  | ||||
| @ -1055,7 +1036,7 @@ async fn serial_test_import_glb_no_assign() { | ||||
| async fn serial_test_import_ext_doesnt_match() { | ||||
|     let code = r#"const model = import("tests/executor/inputs/cube.gltf", {type: "obj", units: "m"})"#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1080,7 +1061,7 @@ async fn serial_test_cube_mm() { | ||||
| const myCube = cube([0,0], 10) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/cube_mm.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1213,7 +1194,7 @@ const part002 = startSketchOn(part001, "here") | ||||
|   |> extrude(1, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|  | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
| @ -1254,7 +1235,7 @@ const part003 = startSketchOn(part002, "end") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_face.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1271,7 +1252,7 @@ async fn serial_test_stdlib_kcl_error_right_code_path() { | ||||
|   |> extrude(2, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1299,7 +1280,7 @@ const part002 = startSketchOn(part001, "end") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1323,7 +1304,7 @@ const part002 = startSketchOn(part001, "end") | ||||
|   |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle_tagged.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1365,7 +1346,7 @@ const part = rectShape([0, 0], 20, 20) | ||||
|      }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1386,7 +1367,7 @@ async fn serial_test_big_number_angle_to_match_length_x() { | ||||
|   |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/big_number_angle_to_match_length_x.png", | ||||
|         &result, | ||||
| @ -1407,7 +1388,7 @@ async fn serial_test_big_number_angle_to_match_length_y() { | ||||
|   |> extrude(10, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image( | ||||
|         "tests/executor/outputs/big_number_angle_to_match_length_y.png", | ||||
|         &result, | ||||
| @ -1431,7 +1412,7 @@ async fn serial_test_simple_revolve() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1451,7 +1432,7 @@ async fn serial_test_simple_revolve_uppercase() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_uppercase.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1471,7 +1452,7 @@ async fn serial_test_simple_revolve_negative() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_negative.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1491,7 +1472,7 @@ async fn serial_test_revolve_bad_angle_low() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|  | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
| @ -1516,7 +1497,7 @@ async fn serial_test_revolve_bad_angle_high() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|  | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
| @ -1541,7 +1522,7 @@ async fn serial_test_simple_revolve_custom_angle() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_angle.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1561,7 +1542,7 @@ async fn serial_test_simple_revolve_custom_axis() { | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_axis.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1585,7 +1566,7 @@ const sketch001 = startSketchOn(box, "end") | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/revolve_on_edge.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1609,7 +1590,7 @@ const sketch001 = startSketchOn(box, "revolveAxis") | ||||
|  | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|  | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
| @ -1636,7 +1617,7 @@ const sketch001 = startSketchOn(box, "END") | ||||
|     }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle_edge.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1658,7 +1639,7 @@ const sketch001 = startSketchOn(box, "END") | ||||
|     }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1684,7 +1665,7 @@ const sketch001 = startSketchOn(box, "end") | ||||
|   }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1698,7 +1679,7 @@ async fn serial_test_basic_revolve_circle() { | ||||
|     }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/basic_revolve_circle.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1725,7 +1706,7 @@ const part002 = startSketchOn(part001, 'end') | ||||
|     |> extrude(5, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_sketch_on_edge.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1786,7 +1767,7 @@ const plumbus1 = make_circle(p, 'b', [0, 0], 2.5) | ||||
|       }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1794,7 +1775,7 @@ const plumbus1 = make_circle(p, 'b', [0, 0], 2.5) | ||||
| async fn serial_test_empty_file_is_ok() { | ||||
|     let code = r#""#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_ok()); | ||||
| } | ||||
|  | ||||
| @ -1824,7 +1805,7 @@ async fn serial_test_member_expression_in_params() { | ||||
| capScrew([0, 0.5, 0], 50, 37.5, 50, 25) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1869,7 +1850,7 @@ const bracket = startSketchOn('XY') | ||||
|      }, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1893,7 +1874,7 @@ const secondSketch = startSketchOn(part001, '') | ||||
|   |> extrude(20, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1924,7 +1905,7 @@ const extrusion = startSketchOn('XY') | ||||
|   |> extrude(height, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await; | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await; | ||||
|     assert!(result.is_err()); | ||||
|     assert_eq!( | ||||
|         result.err().unwrap().to_string(), | ||||
| @ -1942,7 +1923,7 @@ async fn serial_test_xz_plane() { | ||||
|   |> extrude(5 + 7, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/xz_plane.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| @ -1956,6 +1937,38 @@ async fn serial_test_neg_xz_plane() { | ||||
|   |> extrude(5 + 7, %) | ||||
| "#; | ||||
|  | ||||
|     let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); | ||||
|     let result = snapshot_on_test_server(code.to_owned(), test_name!()).await.unwrap(); | ||||
|     twenty_twenty::assert_image("tests/executor/outputs/neg_xz_plane.png", &result, 1.0); | ||||
| } | ||||
|  | ||||
| async fn snapshot_on_test_server(code: String, test_name: &str) -> Result<image::DynamicImage> { | ||||
|     let default_addr = "0.0.0.0:3333"; | ||||
|     let server_url = std::env::var("TEST_EXECUTOR_ADDR").unwrap_or(default_addr.to_owned()); | ||||
|     let client = reqwest::Client::new(); | ||||
|     let body = serde_json::json!({ | ||||
|       "kcl_program": code, | ||||
|       "test_name": test_name, | ||||
|     }); | ||||
|     let body = serde_json::to_vec(&body).unwrap(); | ||||
|     let resp = match client.post(format!("http://{server_url}")).body(body).send().await { | ||||
|         Ok(r) => r, | ||||
|         Err(e) if e.is_connect() => { | ||||
|             eprintln!("Received a connection error. Is the test server running?"); | ||||
|             eprintln!("If so, is it running at {server_url}?"); | ||||
|             panic!("Connection error: {e}"); | ||||
|         } | ||||
|         Err(e) => panic!("{e}"), | ||||
|     }; | ||||
|     let status = resp.status(); | ||||
|     let bytes = resp.bytes().await?; | ||||
|     if status.is_success() { | ||||
|         let img = ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; | ||||
|         Ok(img) | ||||
|     } else if status == hyper::StatusCode::BAD_GATEWAY { | ||||
|         let err = String::from_utf8(bytes.into()).unwrap(); | ||||
|         anyhow::bail!("{err}") | ||||
|     } else { | ||||
|         let err = String::from_utf8(bytes.into()).unwrap(); | ||||
|         panic!("{err}"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 128 KiB | 
| Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB | 
| Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB | 
| Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 124 KiB | 
| Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB | 
| Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 289 KiB | 
| Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 90 KiB | 
| Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB | 
| Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB | 
| Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 124 KiB | 
| Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 141 KiB | 
| Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 80 KiB | 
| Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 120 KiB | 
| Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB | 
| Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 108 KiB | 
| Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 210 KiB | 
| Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 210 KiB | 
| Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 214 KiB | 
| Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 183 KiB | 
| Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 112 KiB | 
| Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 94 KiB | 
| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 176 KiB | 
| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB | 
| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB | 
| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB | 
| Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB | 
| Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB | 
| Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 162 KiB | 
| Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB | 
| Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 124 KiB | 
| Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 93 KiB | 
| Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 104 KiB | 
| Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 112 KiB | 
| Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 113 KiB | 
| Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 115 KiB | 
| Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 122 KiB | 
| Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 109 KiB | 
| Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 71 KiB | 
| Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 138 KiB | 
| Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 129 KiB | 
| Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 105 KiB | 
| Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 87 KiB | 
| Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 144 KiB | 
| Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB | 
| Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 166 KiB | 
| Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 139 KiB | 
| Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 91 KiB | 
| Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB | 
| Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB | 
| Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 181 KiB | 
| Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 170 KiB | 
| Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 146 KiB | 
| Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 190 KiB | 
| Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 157 KiB | 
| Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB | 
| Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 164 KiB | 
| Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 150 KiB | 
| Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB | 
| Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB | 
| Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 152 KiB | 
| Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 115 KiB | 
