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