1use std::sync::Arc;
4
5use indexmap::IndexMap;
6use itertools::EitherOrBoth;
7use itertools::Itertools;
8use tokio::sync::RwLock;
9
10use crate::ExecOutcome;
11use crate::ExecutorContext;
12use crate::errors::KclError;
13use crate::execution::ConstraintKey;
14use crate::execution::ConstraintState;
15use crate::execution::EnvironmentRef;
16use crate::execution::ExecutorSettings;
17use crate::execution::KclValueView;
18use crate::execution::annotations;
19use crate::execution::memory::Stack;
20use crate::execution::state::ModuleInfoMap;
21use crate::execution::state::{self as exec_state};
22use crate::front::Object;
23use crate::front::ObjectId;
24use crate::modules::ModuleId;
25use crate::modules::ModulePath;
26use crate::modules::ModuleSource;
27use crate::parsing::ast::types::Annotation;
28use crate::parsing::ast::types::Node;
29use crate::parsing::ast::types::Program;
30use crate::walk::Node as WalkNode;
31
32lazy_static::lazy_static! {
33 static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
35 static ref PREV_MEMORY: Arc<RwLock<Option<SketchModeState>>> = Default::default();
37}
38
39pub(super) async fn read_old_ast() -> Option<GlobalState> {
41 let old_ast = OLD_AST.read().await;
42 old_ast.clone()
43}
44
45pub(super) async fn write_old_ast(old_state: GlobalState) {
46 let mut old_ast = OLD_AST.write().await;
47 *old_ast = Some(old_state);
48}
49
50pub(crate) async fn read_old_memory() -> Option<SketchModeState> {
51 let old_mem = PREV_MEMORY.read().await;
52 old_mem.clone()
53}
54
55pub(crate) async fn write_old_memory(mem: SketchModeState) {
56 let mut old_mem = PREV_MEMORY.write().await;
57 *old_mem = Some(mem);
58}
59
60pub async fn bust_cache() {
61 let mut old_ast = OLD_AST.write().await;
62 *old_ast = None;
63}
64
65pub async fn clear_mem_cache() {
66 let mut old_mem = PREV_MEMORY.write().await;
67 *old_mem = None;
68}
69
70#[derive(Debug, Clone)]
72pub struct CacheInformation<'a> {
73 pub ast: &'a Node<Program>,
74 pub settings: &'a ExecutorSettings,
75}
76
77#[derive(Debug, Clone)]
79pub(super) struct GlobalState {
80 pub(super) main: ModuleState,
81 pub(super) exec_state: exec_state::GlobalState,
83 pub(super) settings: ExecutorSettings,
85}
86
87impl GlobalState {
88 pub fn new(
89 state: exec_state::ExecState,
90 settings: ExecutorSettings,
91 ast: Node<Program>,
92 result_env: EnvironmentRef,
93 ) -> Self {
94 Self {
95 main: ModuleState {
96 ast,
97 exec_state: state.mod_local,
98 result_env,
99 },
100 exec_state: state.global,
101 settings,
102 }
103 }
104
105 pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
106 self.settings = settings;
107 self
108 }
109
110 pub fn reconstitute_exec_state(&self, ctx: &ExecutorContext) -> exec_state::ExecState {
111 exec_state::ExecState {
112 execution_callbacks: ctx.execution_callbacks.clone(),
113 global: self.exec_state.clone(),
114 mod_local: self.main.exec_state.clone(),
115 }
116 }
117
118 pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> Result<ExecOutcome, KclError> {
119 let variables = self
122 .main
123 .exec_state
124 .variables(self.main.result_env)?
125 .into_iter()
126 .map(|(key, value)| (key, KclValueView::from(value)))
127 .collect();
128 Ok(ExecOutcome {
129 variables,
130 filenames: self.exec_state.filenames(),
131 operations: self.exec_state.operations_by_module(),
132 artifact_graph: self.exec_state.artifacts.graph,
133 scene_objects: self.exec_state.root_module_artifacts.scene_objects,
134 source_range_to_object: self.exec_state.root_module_artifacts.source_range_to_object,
135 var_solutions: self.exec_state.root_module_artifacts.var_solutions,
136 refactor_metadata: self.exec_state.root_module_artifacts.refactor_metadata.clone(),
137 issues: self.exec_state.issues,
138 default_planes: ctx.engine.get_default_planes().read().await.clone(),
139 })
140 }
141
142 pub fn mock_memory_state(&self) -> Result<SketchModeState, KclError> {
143 let mut stack = self.main.exec_state.stack.deep_clone()?;
144 stack.restore_env(self.main.result_env)?;
145
146 Ok(SketchModeState {
147 stack,
148 module_infos: self.exec_state.module_infos.clone(),
149 path_to_source_id: self.exec_state.path_to_source_id.clone(),
150 id_to_source: self.exec_state.id_to_source.clone(),
151 constraint_state: self.main.exec_state.constraint_state.clone(),
152 scene_objects: self.exec_state.root_module_artifacts.scene_objects.clone(),
153 })
154 }
155}
156
157#[derive(Debug, Clone)]
159pub(super) struct ModuleState {
160 pub(super) ast: Node<Program>,
162 pub(super) exec_state: exec_state::ModuleState,
164 pub(super) result_env: EnvironmentRef,
166}
167
168#[derive(Debug, Clone)]
170pub(crate) struct SketchModeState {
171 pub stack: Stack,
173 pub module_infos: ModuleInfoMap,
175 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
177 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
179 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
181 pub scene_objects: Vec<Object>,
183}
184
185#[cfg(test)]
186impl SketchModeState {
187 pub(crate) fn new_for_tests() -> Self {
188 Self {
189 stack: Stack::new_for_tests(),
190 module_infos: ModuleInfoMap::default(),
191 path_to_source_id: Default::default(),
192 id_to_source: Default::default(),
193 constraint_state: Default::default(),
194 scene_objects: Vec::new(),
195 }
196 }
197}
198
199#[derive(Debug, Clone, PartialEq)]
201#[allow(clippy::large_enum_variant)]
202pub(super) enum CacheResult {
203 ReExecute {
204 clear_scene: bool,
206 reapply_settings: bool,
208 program: Node<Program>,
210 },
211 CheckImportsOnly {
215 reapply_settings: bool,
217 ast: Node<Program>,
219 },
220 NoAction(bool),
222}
223
224pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult {
232 let mut reapply_settings = false;
233
234 if old.settings != new.settings {
237 reapply_settings = true;
240 }
241
242 if old.ast == new.ast {
245 if !old.ast.has_import_statements() {
249 return CacheResult::NoAction(reapply_settings);
250 }
251
252 return CacheResult::CheckImportsOnly {
254 reapply_settings,
255 ast: old.ast.clone(),
256 };
257 }
258
259 let mut old_ast = old.ast.clone();
261 let mut new_ast = new.ast.clone();
262
263 old_ast.compute_digest();
266 new_ast.compute_digest();
267
268 if old_ast.digest == new_ast.digest {
270 if !old.ast.has_import_statements() {
274 return CacheResult::NoAction(reapply_settings);
275 }
276
277 return CacheResult::CheckImportsOnly {
279 reapply_settings,
280 ast: old.ast.clone(),
281 };
282 }
283
284 if !old_ast
286 .inner_attrs
287 .iter()
288 .filter(annotations::is_significant)
289 .zip_longest(new_ast.inner_attrs.iter().filter(annotations::is_significant))
290 .all(|pair| {
291 match pair {
292 EitherOrBoth::Both(old, new) => {
293 let Annotation { name, properties, .. } = &old.inner;
296 let Annotation {
297 name: new_name,
298 properties: new_properties,
299 ..
300 } = &new.inner;
301
302 name.as_ref().map(|n| n.digest) == new_name.as_ref().map(|n| n.digest)
303 && properties
304 .as_ref()
305 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
306 == new_properties
307 .as_ref()
308 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
309 }
310 _ => false,
311 }
312 })
313 {
314 return CacheResult::ReExecute {
318 clear_scene: true,
319 reapply_settings: true,
320 program: new.ast.clone(),
321 };
322 }
323
324 generate_changed_program(old_ast, new_ast, reapply_settings)
326}
327
328fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>, reapply_settings: bool) -> CacheResult {
340 if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
341 let old_node: WalkNode = old.into();
342 let new_node: WalkNode = new.into();
343 old_node.digest() == new_node.digest()
344 }) {
345 return CacheResult::ReExecute {
351 clear_scene: true,
352 reapply_settings,
353 program: new_ast,
354 };
355 }
356
357 match new_ast.body.len().cmp(&old_ast.body.len()) {
361 std::cmp::Ordering::Less => {
362 CacheResult::ReExecute {
371 clear_scene: true,
372 reapply_settings,
373 program: new_ast,
374 }
375 }
376 std::cmp::Ordering::Greater => {
377 new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
385
386 CacheResult::ReExecute {
387 clear_scene: false,
388 reapply_settings,
389 program: new_ast,
390 }
391 }
392 std::cmp::Ordering::Equal => {
393 CacheResult::NoAction(reapply_settings)
403 }
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use pretty_assertions::assert_eq;
410
411 use super::*;
412 use crate::execution::ExecTestResults;
413 use crate::execution::parse_execute;
414 use crate::execution::parse_execute_with_project_dir;
415
416 #[tokio::test(flavor = "multi_thread")]
417 async fn test_get_changed_program_same_code() {
418 let new = r#"// Remove the end face for the extrusion.
419firstSketch = startSketchOn(XY)
420 |> startProfile(at = [-12, 12])
421 |> line(end = [24, 0])
422 |> line(end = [0, -24])
423 |> line(end = [-24, 0])
424 |> close()
425 |> extrude(length = 6)
426
427// Remove the end face for the extrusion.
428shell(firstSketch, faces = [END], thickness = 0.25)"#;
429
430 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
431
432 let result = get_changed_program(
433 CacheInformation {
434 ast: &program.ast,
435 settings: &exec_ctxt.settings,
436 },
437 CacheInformation {
438 ast: &program.ast,
439 settings: &exec_ctxt.settings,
440 },
441 )
442 .await;
443
444 assert_eq!(result, CacheResult::NoAction(false));
445 exec_ctxt.close().await;
446 }
447
448 #[tokio::test(flavor = "multi_thread")]
449 async fn test_get_changed_program_same_code_changed_whitespace() {
450 let old = r#" // Remove the end face for the extrusion.
451firstSketch = startSketchOn(XY)
452 |> startProfile(at = [-12, 12])
453 |> line(end = [24, 0])
454 |> line(end = [0, -24])
455 |> line(end = [-24, 0])
456 |> close()
457 |> extrude(length = 6)
458
459// Remove the end face for the extrusion.
460shell(firstSketch, faces = [END], thickness = 0.25) "#;
461
462 let new = r#"// Remove the end face for the extrusion.
463firstSketch = startSketchOn(XY)
464 |> startProfile(at = [-12, 12])
465 |> line(end = [24, 0])
466 |> line(end = [0, -24])
467 |> line(end = [-24, 0])
468 |> close()
469 |> extrude(length = 6)
470
471// Remove the end face for the extrusion.
472shell(firstSketch, faces = [END], thickness = 0.25)"#;
473
474 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
475
476 let program_new = crate::Program::parse_no_errs(new).unwrap();
477
478 let result = get_changed_program(
479 CacheInformation {
480 ast: &program.ast,
481 settings: &exec_ctxt.settings,
482 },
483 CacheInformation {
484 ast: &program_new.ast,
485 settings: &exec_ctxt.settings,
486 },
487 )
488 .await;
489
490 assert_eq!(result, CacheResult::NoAction(false));
491 exec_ctxt.close().await;
492 }
493
494 #[tokio::test(flavor = "multi_thread")]
495 async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() {
496 let old = r#" // Removed the end face for the extrusion.
497firstSketch = startSketchOn(XY)
498 |> startProfile(at = [-12, 12])
499 |> line(end = [24, 0])
500 |> line(end = [0, -24])
501 |> line(end = [-24, 0])
502 |> close()
503 |> extrude(length = 6)
504
505// Remove the end face for the extrusion.
506shell(firstSketch, faces = [END], thickness = 0.25) "#;
507
508 let new = r#"// Remove the end face for the extrusion.
509firstSketch = startSketchOn(XY)
510 |> startProfile(at = [-12, 12])
511 |> line(end = [24, 0])
512 |> line(end = [0, -24])
513 |> line(end = [-24, 0])
514 |> close()
515 |> extrude(length = 6)
516
517// Remove the end face for the extrusion.
518shell(firstSketch, faces = [END], thickness = 0.25)"#;
519
520 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
521
522 let program_new = crate::Program::parse_no_errs(new).unwrap();
523
524 let result = get_changed_program(
525 CacheInformation {
526 ast: &program.ast,
527 settings: &exec_ctxt.settings,
528 },
529 CacheInformation {
530 ast: &program_new.ast,
531 settings: &exec_ctxt.settings,
532 },
533 )
534 .await;
535
536 assert_eq!(result, CacheResult::NoAction(false));
537 exec_ctxt.close().await;
538 }
539
540 #[tokio::test(flavor = "multi_thread")]
541 async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
542 let old = r#"@foo(whatever = whatever)
543@bar
544// Removed the end face for the extrusion.
545firstSketch = startSketchOn(XY)
546 |> startProfile(at = [-12, 12])
547 |> line(end = [24, 0])
548 |> line(end = [0, -24])
549 |> line(end = [-24, 0]) // my thing
550 |> close()
551 |> extrude(length = 6)
552
553// Remove the end face for the extrusion.
554shell(firstSketch, faces = [END], thickness = 0.25) "#;
555
556 let new = r#"@foo(whatever = 42)
557@baz
558// Remove the end face for the extrusion.
559firstSketch = startSketchOn(XY)
560 |> startProfile(at = [-12, 12])
561 |> line(end = [24, 0])
562 |> line(end = [0, -24])
563 |> line(end = [-24, 0])
564 |> close()
565 |> extrude(length = 6)
566
567// Remove the end face for the extrusion.
568shell(firstSketch, faces = [END], thickness = 0.25)"#;
569
570 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
571
572 let program_new = crate::Program::parse_no_errs(new).unwrap();
573
574 let result = get_changed_program(
575 CacheInformation {
576 ast: &program.ast,
577 settings: &exec_ctxt.settings,
578 },
579 CacheInformation {
580 ast: &program_new.ast,
581 settings: &exec_ctxt.settings,
582 },
583 )
584 .await;
585
586 assert_eq!(result, CacheResult::NoAction(false));
587 exec_ctxt.close().await;
588 }
589
590 #[tokio::test(flavor = "multi_thread")]
592 async fn test_get_changed_program_same_code_but_different_grid_setting() {
593 let new = r#"// Remove the end face for the extrusion.
594firstSketch = startSketchOn(XY)
595 |> startProfile(at = [-12, 12])
596 |> line(end = [24, 0])
597 |> line(end = [0, -24])
598 |> line(end = [-24, 0])
599 |> close()
600 |> extrude(length = 6)
601
602// Remove the end face for the extrusion.
603shell(firstSketch, faces = [END], thickness = 0.25)"#;
604
605 let ExecTestResults {
606 program, mut exec_ctxt, ..
607 } = parse_execute(new).await.unwrap();
608
609 exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
611
612 let result = get_changed_program(
613 CacheInformation {
614 ast: &program.ast,
615 settings: &Default::default(),
616 },
617 CacheInformation {
618 ast: &program.ast,
619 settings: &exec_ctxt.settings,
620 },
621 )
622 .await;
623
624 assert_eq!(result, CacheResult::NoAction(true));
625 exec_ctxt.close().await;
626 }
627
628 #[tokio::test(flavor = "multi_thread")]
630 async fn test_get_changed_program_same_code_but_different_edge_visibility_setting() {
631 let new = r#"// Remove the end face for the extrusion.
632firstSketch = startSketchOn(XY)
633 |> startProfile(at = [-12, 12])
634 |> line(end = [24, 0])
635 |> line(end = [0, -24])
636 |> line(end = [-24, 0])
637 |> close()
638 |> extrude(length = 6)
639
640// Remove the end face for the extrusion.
641shell(firstSketch, faces = [END], thickness = 0.25)"#;
642
643 let ExecTestResults {
644 program, mut exec_ctxt, ..
645 } = parse_execute(new).await.unwrap();
646
647 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
649
650 let result = get_changed_program(
651 CacheInformation {
652 ast: &program.ast,
653 settings: &Default::default(),
654 },
655 CacheInformation {
656 ast: &program.ast,
657 settings: &exec_ctxt.settings,
658 },
659 )
660 .await;
661
662 assert_eq!(result, CacheResult::NoAction(true));
663
664 let old_settings = exec_ctxt.settings.clone();
666 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
667
668 let result = get_changed_program(
669 CacheInformation {
670 ast: &program.ast,
671 settings: &old_settings,
672 },
673 CacheInformation {
674 ast: &program.ast,
675 settings: &exec_ctxt.settings,
676 },
677 )
678 .await;
679
680 assert_eq!(result, CacheResult::NoAction(true));
681
682 let old_settings = exec_ctxt.settings.clone();
684 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
685
686 let result = get_changed_program(
687 CacheInformation {
688 ast: &program.ast,
689 settings: &old_settings,
690 },
691 CacheInformation {
692 ast: &program.ast,
693 settings: &exec_ctxt.settings,
694 },
695 )
696 .await;
697
698 assert_eq!(result, CacheResult::NoAction(true));
699 exec_ctxt.close().await;
700 }
701
702 #[tokio::test(flavor = "multi_thread")]
705 async fn test_get_changed_program_same_code_but_different_unit_setting_using_annotation() {
706 let old_code = r#"@settings(defaultLengthUnit = in)
707startSketchOn(XY)
708"#;
709 let new_code = r#"@settings(defaultLengthUnit = mm)
710startSketchOn(XY)
711"#;
712
713 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
714
715 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
716 new_program.compute_digest();
717
718 let result = get_changed_program(
719 CacheInformation {
720 ast: &program.ast,
721 settings: &exec_ctxt.settings,
722 },
723 CacheInformation {
724 ast: &new_program.ast,
725 settings: &exec_ctxt.settings,
726 },
727 )
728 .await;
729
730 assert_eq!(
731 result,
732 CacheResult::ReExecute {
733 clear_scene: true,
734 reapply_settings: true,
735 program: new_program.ast,
736 }
737 );
738 exec_ctxt.close().await;
739 }
740
741 #[tokio::test(flavor = "multi_thread")]
744 async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
745 let old_code = r#"@settings(defaultLengthUnit = in)
746startSketchOn(XY)
747"#;
748 let new_code = r#"
749startSketchOn(XY)
750"#;
751
752 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
753
754 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
755 new_program.compute_digest();
756
757 let result = get_changed_program(
758 CacheInformation {
759 ast: &program.ast,
760 settings: &exec_ctxt.settings,
761 },
762 CacheInformation {
763 ast: &new_program.ast,
764 settings: &exec_ctxt.settings,
765 },
766 )
767 .await;
768
769 assert_eq!(
770 result,
771 CacheResult::ReExecute {
772 clear_scene: true,
773 reapply_settings: true,
774 program: new_program.ast,
775 }
776 );
777 exec_ctxt.close().await;
778 }
779
780 #[tokio::test(flavor = "multi_thread")]
781 async fn test_multi_file_no_changes_does_not_reexecute() {
782 let code = r#"import "toBeImported.kcl" as importedCube
783
784importedCube
785
786sketch001 = startSketchOn(XZ)
787profile001 = startProfile(sketch001, at = [-134.53, -56.17])
788 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
789 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
790 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
791 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
792 |> close()
793extrude001 = extrude(profile001, length = 100)
794sketch003 = startSketchOn(extrude001, face = seg02)
795sketch002 = startSketchOn(extrude001, face = seg01)
796"#;
797
798 let other_file = (
799 std::path::PathBuf::from("toBeImported.kcl"),
800 r#"sketch001 = startSketchOn(XZ)
801profile001 = startProfile(sketch001, at = [281.54, 305.81])
802 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
803 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
804 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
805 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
806 |> close()
807extrude(profile001, length = 100)"#
808 .to_string(),
809 );
810
811 let tmp_dir = std::env::temp_dir();
812 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
813
814 let tmp_file = tmp_dir.join(other_file.0);
816 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
817 std::fs::write(tmp_file, other_file.1).unwrap();
818
819 let ExecTestResults { program, exec_ctxt, .. } =
820 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
821 .await
822 .unwrap();
823
824 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
825 new_program.compute_digest();
826
827 let result = get_changed_program(
828 CacheInformation {
829 ast: &program.ast,
830 settings: &exec_ctxt.settings,
831 },
832 CacheInformation {
833 ast: &new_program.ast,
834 settings: &exec_ctxt.settings,
835 },
836 )
837 .await;
838
839 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
840 panic!("Expected CheckImportsOnly, got {result:?}");
841 };
842
843 assert_eq!(reapply_settings, false);
844 exec_ctxt.close().await;
845 }
846
847 #[tokio::test(flavor = "multi_thread")]
848 async fn test_cache_multi_file_only_other_file_changes_should_reexecute() {
849 let code = r#"import "toBeImported.kcl" as importedCube
850
851importedCube
852
853sketch001 = startSketchOn(XZ)
854profile001 = startProfile(sketch001, at = [-134.53, -56.17])
855 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
856 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
857 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
858 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
859 |> close()
860extrude001 = extrude(profile001, length = 100)
861sketch003 = startSketchOn(extrude001, face = seg02)
862sketch002 = startSketchOn(extrude001, face = seg01)
863"#;
864
865 let other_file = (
866 std::path::PathBuf::from("toBeImported.kcl"),
867 r#"sketch001 = startSketchOn(XZ)
868profile001 = startProfile(sketch001, at = [281.54, 305.81])
869 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
870 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
871 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
872 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
873 |> close()
874extrude(profile001, length = 100)"#
875 .to_string(),
876 );
877
878 let other_file2 = (
879 std::path::PathBuf::from("toBeImported.kcl"),
880 r#"sketch001 = startSketchOn(XZ)
881profile001 = startProfile(sketch001, at = [281.54, 305.81])
882 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
883 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
884 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
885 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
886 |> close()
887extrude(profile001, length = 100)
888|> translate(z=100)
889"#
890 .to_string(),
891 );
892
893 let tmp_dir = std::env::temp_dir();
894 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
895
896 let tmp_file = tmp_dir.join(other_file.0);
898 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
899 std::fs::write(&tmp_file, other_file.1).unwrap();
900
901 let ExecTestResults { program, exec_ctxt, .. } =
902 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
903 .await
904 .unwrap();
905
906 std::fs::write(tmp_file, other_file2.1).unwrap();
908
909 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
910 new_program.compute_digest();
911
912 let result = get_changed_program(
913 CacheInformation {
914 ast: &program.ast,
915 settings: &exec_ctxt.settings,
916 },
917 CacheInformation {
918 ast: &new_program.ast,
919 settings: &exec_ctxt.settings,
920 },
921 )
922 .await;
923
924 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
925 panic!("Expected CheckImportsOnly, got {result:?}");
926 };
927
928 assert_eq!(reapply_settings, false);
929 exec_ctxt.close().await;
930 }
931
932 #[tokio::test(flavor = "multi_thread")]
933 async fn test_get_changed_program_added_outer_attribute() {
934 let old_code = r#"import "tests/inputs/cube.step"
935"#;
936 let new_code = r#"@(coords = opengl)
937import "tests/inputs/cube.step"
938"#;
939
940 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
941
942 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
943 new_program.compute_digest();
944
945 let result = get_changed_program(
946 CacheInformation {
947 ast: &program.ast,
948 settings: &exec_ctxt.settings,
949 },
950 CacheInformation {
951 ast: &new_program.ast,
952 settings: &exec_ctxt.settings,
953 },
954 )
955 .await;
956
957 assert_eq!(
958 result,
959 CacheResult::ReExecute {
960 clear_scene: true,
961 reapply_settings: false,
962 program: new_program.ast,
963 }
964 );
965 exec_ctxt.close().await;
966 }
967
968 #[tokio::test(flavor = "multi_thread")]
969 async fn test_get_changed_program_different_outer_attribute() {
970 let old_code = r#"@(coords = vulkan)
971import "tests/inputs/cube.step"
972"#;
973 let new_code = r#"@(coords = opengl)
974import "tests/inputs/cube.step"
975"#;
976
977 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
978
979 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
980 new_program.compute_digest();
981
982 let result = get_changed_program(
983 CacheInformation {
984 ast: &program.ast,
985 settings: &exec_ctxt.settings,
986 },
987 CacheInformation {
988 ast: &new_program.ast,
989 settings: &exec_ctxt.settings,
990 },
991 )
992 .await;
993
994 assert_eq!(
995 result,
996 CacheResult::ReExecute {
997 clear_scene: true,
998 reapply_settings: false,
999 program: new_program.ast,
1000 }
1001 );
1002 exec_ctxt.close().await;
1003 }
1004}