From e489222b6a0d24649ab3d208839114879161bdbb Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Thu, 15 May 2025 12:35:29 -0700 Subject: [PATCH] expose mock executing to python library; (#6980) * expose mock executing to python library; Signed-off-by: Jess Frazelle * bump Signed-off-by: Jess Frazelle * Update rust/kcl-python-bindings/src/lib.rs Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --------- Signed-off-by: Jess Frazelle Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- rust/Cargo.lock | 20 +++--- rust/kcl-bumper/Cargo.toml | 2 +- rust/kcl-derive-docs/Cargo.toml | 2 +- rust/kcl-directory-test-macro/Cargo.toml | 2 +- rust/kcl-language-server-release/Cargo.toml | 2 +- rust/kcl-language-server/Cargo.toml | 2 +- rust/kcl-lib/Cargo.toml | 2 +- rust/kcl-lib/src/execution/mod.rs | 6 +- rust/kcl-lib/src/execution/types.rs | 16 ++--- rust/kcl-lib/src/std/patterns.rs | 4 +- rust/kcl-lib/src/walk/import_graph.rs | 8 +-- rust/kcl-python-bindings/Cargo.toml | 2 +- rust/kcl-python-bindings/src/lib.rs | 72 ++++++++++++++++++--- rust/kcl-python-bindings/tests/tests.py | 29 +++++++++ rust/kcl-test-server/Cargo.toml | 2 +- rust/kcl-to-core/Cargo.toml | 2 +- rust/kcl-wasm-lib/Cargo.toml | 2 +- 17 files changed, 130 insertions(+), 45 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4b8e63824..eac9e33ee 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1815,7 +1815,7 @@ dependencies = [ [[package]] name = "kcl-bumper" -version = "0.1.72" +version = "0.1.73" dependencies = [ "anyhow", "clap", @@ -1826,7 +1826,7 @@ dependencies = [ [[package]] name = "kcl-derive-docs" -version = "0.1.72" +version = "0.1.73" dependencies = [ "Inflector", "anyhow", @@ -1845,7 +1845,7 @@ dependencies = [ [[package]] name = "kcl-directory-test-macro" -version = "0.1.72" +version = "0.1.73" dependencies = [ "proc-macro2", "quote", @@ -1854,7 +1854,7 @@ dependencies = [ [[package]] name = "kcl-language-server" -version = "0.2.72" +version = "0.2.73" dependencies = [ "anyhow", "clap", @@ -1875,7 +1875,7 @@ dependencies = [ [[package]] name = "kcl-language-server-release" -version = "0.1.72" +version = "0.1.73" dependencies = [ "anyhow", "clap", @@ -1895,7 +1895,7 @@ dependencies = [ [[package]] name = "kcl-lib" -version = "0.2.72" +version = "0.2.73" dependencies = [ "anyhow", "approx 0.5.1", @@ -1971,7 +1971,7 @@ dependencies = [ [[package]] name = "kcl-python-bindings" -version = "0.3.72" +version = "0.3.73" dependencies = [ "anyhow", "kcl-lib", @@ -1986,7 +1986,7 @@ dependencies = [ [[package]] name = "kcl-test-server" -version = "0.1.72" +version = "0.1.73" dependencies = [ "anyhow", "hyper 0.14.32", @@ -1999,7 +1999,7 @@ dependencies = [ [[package]] name = "kcl-to-core" -version = "0.1.72" +version = "0.1.73" dependencies = [ "anyhow", "async-trait", @@ -2013,7 +2013,7 @@ dependencies = [ [[package]] name = "kcl-wasm-lib" -version = "0.1.72" +version = "0.1.73" dependencies = [ "anyhow", "bson", diff --git a/rust/kcl-bumper/Cargo.toml b/rust/kcl-bumper/Cargo.toml index f213f1130..8026c4f6c 100644 --- a/rust/kcl-bumper/Cargo.toml +++ b/rust/kcl-bumper/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-bumper" -version = "0.1.72" +version = "0.1.73" edition = "2021" repository = "https://github.com/KittyCAD/modeling-api" rust-version = "1.76" diff --git a/rust/kcl-derive-docs/Cargo.toml b/rust/kcl-derive-docs/Cargo.toml index 9a8555d8d..2f25a23f0 100644 --- a/rust/kcl-derive-docs/Cargo.toml +++ b/rust/kcl-derive-docs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-derive-docs" description = "A tool for generating documentation from Rust derive macros" -version = "0.1.72" +version = "0.1.73" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/rust/kcl-directory-test-macro/Cargo.toml b/rust/kcl-directory-test-macro/Cargo.toml index 051c09163..a5cbc76b4 100644 --- a/rust/kcl-directory-test-macro/Cargo.toml +++ b/rust/kcl-directory-test-macro/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-directory-test-macro" description = "A tool for generating tests from a directory of kcl files" -version = "0.1.72" +version = "0.1.73" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/rust/kcl-language-server-release/Cargo.toml b/rust/kcl-language-server-release/Cargo.toml index 0688819a2..b1a7b8568 100644 --- a/rust/kcl-language-server-release/Cargo.toml +++ b/rust/kcl-language-server-release/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kcl-language-server-release" -version = "0.1.72" +version = "0.1.73" edition = "2021" authors = ["KittyCAD Inc "] publish = false diff --git a/rust/kcl-language-server/Cargo.toml b/rust/kcl-language-server/Cargo.toml index 45f981906..e63950338 100644 --- a/rust/kcl-language-server/Cargo.toml +++ b/rust/kcl-language-server/Cargo.toml @@ -2,7 +2,7 @@ name = "kcl-language-server" description = "A language server for KCL." authors = ["KittyCAD Inc "] -version = "0.2.72" +version = "0.2.73" edition = "2021" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust/kcl-lib/Cargo.toml b/rust/kcl-lib/Cargo.toml index 4ad3dfe5d..69159aeb7 100644 --- a/rust/kcl-lib/Cargo.toml +++ b/rust/kcl-lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-lib" description = "KittyCAD Language implementation and tools" -version = "0.2.72" +version = "0.2.73" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/rust/kcl-lib/src/execution/mod.rs b/rust/kcl-lib/src/execution/mod.rs index 44ad8c082..d49d3ecaa 100644 --- a/rust/kcl-lib/src/execution/mod.rs +++ b/rust/kcl-lib/src/execution/mod.rs @@ -426,14 +426,14 @@ impl ExecutorContext { } #[cfg(not(target_arch = "wasm32"))] - pub async fn new_mock() -> Self { + pub async fn new_mock(settings: Option) -> Self { ExecutorContext { engine: Arc::new(Box::new( crate::engine::conn_mock::EngineConnection::new().await.unwrap(), )), fs: Arc::new(FileManager::new()), stdlib: Arc::new(StdLib::new()), - settings: Default::default(), + settings: settings.unwrap_or_default(), context_type: ContextType::Mock, } } @@ -2232,7 +2232,7 @@ w = f() + f() let result = ctx.run_with_caching(program).await.unwrap(); assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0); - let ctx2 = ExecutorContext::new_mock().await; + let ctx2 = ExecutorContext::new_mock(None).await; let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap(); let result = ctx2.run_mock(program2, true).await.unwrap(); assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0); diff --git a/rust/kcl-lib/src/execution/types.rs b/rust/kcl-lib/src/execution/types.rs index d2754e4a6..a90afd9ea 100644 --- a/rust/kcl-lib/src/execution/types.rs +++ b/rust/kcl-lib/src/execution/types.rs @@ -1460,7 +1460,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_idempotent() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let values = values(&mut exec_state); for v in &values { // Identity subtype @@ -1550,7 +1550,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_none() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let none = KclValue::KclNone { value: crate::parsing::ast::types::KclNone::new(), meta: Vec::new(), @@ -1608,7 +1608,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_record() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let obj0 = KclValue::Object { value: HashMap::new(), @@ -1690,7 +1690,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_array() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let hom_arr = KclValue::HomArray { value: vec![ @@ -1843,7 +1843,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_union() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); // Subtyping smaller unions assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![ @@ -1894,7 +1894,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_axes() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); // Subtyping assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d))); @@ -2009,7 +2009,7 @@ mod test { #[tokio::test(flavor = "multi_thread")] async fn coerce_numeric() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let count = KclValue::Number { value: 1.0, @@ -2237,7 +2237,7 @@ d = cos(30) #[tokio::test(flavor = "multi_thread")] async fn coerce_nested_array() { - let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await); let mixed1 = KclValue::HomArray { value: vec![ diff --git a/rust/kcl-lib/src/std/patterns.rs b/rust/kcl-lib/src/std/patterns.rs index d3fda90ea..2d8d7e9cc 100644 --- a/rust/kcl-lib/src/std/patterns.rs +++ b/rust/kcl-lib/src/std/patterns.rs @@ -674,7 +674,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_array_to_point3d() { - let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&ExecutorContext::new_mock(None).await); let input = KclValue::HomArray { value: vec![ KclValue::Number { @@ -706,7 +706,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_tuple_to_point3d() { - let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await); + let mut exec_state = ExecState::new(&ExecutorContext::new_mock(None).await); let input = KclValue::Tuple { value: vec![ KclValue::Number { diff --git a/rust/kcl-lib/src/walk/import_graph.rs b/rust/kcl-lib/src/walk/import_graph.rs index 10d2a84ba..3c9d2c645 100644 --- a/rust/kcl-lib/src/walk/import_graph.rs +++ b/rust/kcl-lib/src/walk/import_graph.rs @@ -267,7 +267,7 @@ import \"a.kcl\" ); modules.insert("b.kcl".to_owned(), into_module_info(b)); - let ctx = ExecutorContext::new_mock().await; + let ctx = ExecutorContext::new_mock(None).await; let order = import_graph(&modules, &ctx).unwrap(); assert_eq!(vec![vec!["a.kcl".to_owned()], vec!["b.kcl".to_owned()]], order); } @@ -290,7 +290,7 @@ x = 1 ); modules.insert("b.kcl".to_owned(), into_module_info(b)); - let ctx = ExecutorContext::new_mock().await; + let ctx = ExecutorContext::new_mock(None).await; let order = import_graph(&modules, &ctx).unwrap(); assert_eq!(vec![vec!["a.kcl".to_owned(), "b.kcl".to_owned()]], order); } @@ -316,7 +316,7 @@ import \"a.kcl\" ); modules.insert("c.kcl".to_owned(), into_module_info(c)); - let ctx = ExecutorContext::new_mock().await; + let ctx = ExecutorContext::new_mock(None).await; let order = import_graph(&modules, &ctx).unwrap(); assert_eq!( vec![vec!["a.kcl".to_owned()], vec!["b.kcl".to_owned(), "c.kcl".to_owned()]], @@ -342,7 +342,7 @@ import \"a.kcl\" ); modules.insert("b.kcl".to_owned(), into_module_info(b)); - let ctx = ExecutorContext::new_mock().await; + let ctx = ExecutorContext::new_mock(None).await; import_graph(&modules, &ctx).unwrap_err(); } } diff --git a/rust/kcl-python-bindings/Cargo.toml b/rust/kcl-python-bindings/Cargo.toml index 6dad7af7d..c423c4987 100644 --- a/rust/kcl-python-bindings/Cargo.toml +++ b/rust/kcl-python-bindings/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kcl-python-bindings" -version = "0.3.72" +version = "0.3.73" edition = "2021" repository = "https://github.com/kittycad/modeling-app" exclude = ["tests/*", "files/*", "venv/*"] diff --git a/rust/kcl-python-bindings/src/lib.rs b/rust/kcl-python-bindings/src/lib.rs index a3e47bfbd..95e74c9da 100644 --- a/rust/kcl-python-bindings/src/lib.rs +++ b/rust/kcl-python-bindings/src/lib.rs @@ -217,12 +217,19 @@ async fn get_code_and_file_path(path: &str) -> Result<(String, std::path::PathBu Ok((code, path)) } -async fn new_context_state(current_file: Option) -> Result<(ExecutorContext, kcl_lib::ExecState)> { +async fn new_context_state( + current_file: Option, + mock: bool, +) -> Result<(ExecutorContext, kcl_lib::ExecState)> { let mut settings: kcl_lib::ExecutorSettings = Default::default(); if let Some(current_file) = current_file { settings.with_current_file(kcl_lib::TypedPath(current_file)); } - let ctx = ExecutorContext::new_with_client(settings, None, None).await?; + let ctx = if mock { + ExecutorContext::new_mock(Some(settings)).await + } else { + ExecutorContext::new_with_client(settings, None, None).await? + }; let state = kcl_lib::ExecState::new(&ctx); Ok((ctx, state)) } @@ -263,7 +270,7 @@ async fn execute(path: String) -> PyResult<()> { let program = kcl_lib::Program::parse_no_errs(&code) .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; - let (ctx, mut state) = new_context_state(Some(path)) + let (ctx, mut state) = new_context_state(Some(path), false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -285,7 +292,7 @@ async fn execute_code(code: String) -> PyResult<()> { let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; - let (ctx, mut state) = new_context_state(None) + let (ctx, mut state) = new_context_state(None, false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -299,6 +306,53 @@ async fn execute_code(code: String) -> PyResult<()> { .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? } +/// Mock execute the kcl code. +#[pyfunction] +async fn mock_execute_code(code: String) -> PyResult { + tokio() + .spawn(async move { + let program = + kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + + let (ctx, mut state) = new_context_state(None, true) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run(&program, &mut state) + .await + .map_err(|err| into_miette(err, &code))?; + + Ok(true) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Mock execute the kcl code from a file path. +#[pyfunction] +async fn mock_execute(path: String) -> PyResult { + tokio() + .spawn(async move { + let (code, path) = get_code_and_file_path(&path) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + let program = kcl_lib::Program::parse_no_errs(&code) + .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; + + let (ctx, mut state) = new_context_state(Some(path), true) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run(&program, &mut state) + .await + .map_err(|err| into_miette(err, &code))?; + + Ok(true) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + /// Execute a kcl file and snapshot it in a specific format. #[pyfunction] async fn execute_and_snapshot(path: String, image_format: ImageFormat) -> PyResult> { @@ -310,7 +364,7 @@ async fn execute_and_snapshot(path: String, image_format: ImageFormat) -> PyResu let program = kcl_lib::Program::parse_no_errs(&code) .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; - let (ctx, mut state) = new_context_state(Some(path)) + let (ctx, mut state) = new_context_state(Some(path), false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -367,7 +421,7 @@ async fn execute_code_and_snapshot(code: String, image_format: ImageFormat) -> P let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; - let (ctx, mut state) = new_context_state(None) + let (ctx, mut state) = new_context_state(None, false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -427,7 +481,7 @@ async fn execute_and_export(path: String, export_format: FileExportFormat) -> Py let program = kcl_lib::Program::parse_no_errs(&code) .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; - let (ctx, mut state) = new_context_state(Some(path.clone())) + let (ctx, mut state) = new_context_state(Some(path.clone()), false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -475,7 +529,7 @@ async fn execute_code_and_export(code: String, export_format: FileExportFormat) let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; - let (ctx, mut state) = new_context_state(None) + let (ctx, mut state) = new_context_state(None, false) .await .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; // Execute the program. @@ -563,6 +617,8 @@ fn kcl(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(parse_code, m)?)?; m.add_function(wrap_pyfunction!(execute, m)?)?; m.add_function(wrap_pyfunction!(execute_code, m)?)?; + m.add_function(wrap_pyfunction!(mock_execute, m)?)?; + m.add_function(wrap_pyfunction!(mock_execute_code, m)?)?; m.add_function(wrap_pyfunction!(execute_and_snapshot, m)?)?; m.add_function(wrap_pyfunction!(execute_code_and_snapshot, m)?)?; m.add_function(wrap_pyfunction!(execute_and_export, m)?)?; diff --git a/rust/kcl-python-bindings/tests/tests.py b/rust/kcl-python-bindings/tests/tests.py index f11fb79f5..fec3ff823 100755 --- a/rust/kcl-python-bindings/tests/tests.py +++ b/rust/kcl-python-bindings/tests/tests.py @@ -68,6 +68,35 @@ async def test_kcl_parse_code(): assert result is True +@pytest.mark.asyncio +async def test_kcl_mock_execute_with_exception(): + # Read from a file. + try: + await kcl.mock_execute(os.path.join(files_dir, "parse_file_error")) + except Exception as e: + assert e is not None + assert len(str(e)) > 0 + assert "lksjndflsskjfnak;jfna##" in str(e) + + +@pytest.mark.asyncio +async def test_kcl_mock_execute(): + # Read from a file. + result = await kcl.mock_execute(lego_file) + assert result is True + + +@pytest.mark.asyncio +async def test_kcl_mock_execute_code(): + # Read from a file. + with open(lego_file, "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + result = await kcl.mock_execute_code(code) + assert result is True + + @pytest.mark.asyncio async def test_kcl_execute_code(): # Read from a file. diff --git a/rust/kcl-test-server/Cargo.toml b/rust/kcl-test-server/Cargo.toml index 43c9b7008..a0aec3330 100644 --- a/rust/kcl-test-server/Cargo.toml +++ b/rust/kcl-test-server/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-test-server" description = "A test server for KCL" -version = "0.1.72" +version = "0.1.73" edition = "2021" license = "MIT" diff --git a/rust/kcl-to-core/Cargo.toml b/rust/kcl-to-core/Cargo.toml index 5bc9b6232..bc7a44461 100644 --- a/rust/kcl-to-core/Cargo.toml +++ b/rust/kcl-to-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-to-core" description = "Utility methods to convert kcl to engine core executable tests" -version = "0.1.72" +version = "0.1.73" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/rust/kcl-wasm-lib/Cargo.toml b/rust/kcl-wasm-lib/Cargo.toml index e2f4eedc0..5b40f3d07 100644 --- a/rust/kcl-wasm-lib/Cargo.toml +++ b/rust/kcl-wasm-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kcl-wasm-lib" -version = "0.1.72" +version = "0.1.73" edition = "2021" repository = "https://github.com/KittyCAD/modeling-app" rust-version = "1.83"