Skip to content

Commit b04cd5d

Browse files
committed
Fixed verb and prop counting errors
1 parent e3bd131 commit b04cd5d

3 files changed

Lines changed: 228 additions & 16 deletions

File tree

vcs-worker/src/object_diff.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -676,46 +676,66 @@ pub fn compare_object_definitions_with_meta(
676676
None
677677
};
678678
// Compare verbs
679+
// Use a map from first verb name to the verb definition to track each verb uniquely
679680
let baseline_verbs: HashMap<String, &moor_compiler::ObjVerbDef> = baseline
680681
.verbs
681682
.iter()
682-
.flat_map(|v| v.names.iter().map(move |name| (name.as_string(), v)))
683+
.filter_map(|v| {
684+
// Use the first name as the identifier for this verb
685+
v.names.first().map(|name| (name.as_string(), v))
686+
})
683687
.collect();
684688

685689
let local_verbs: HashMap<String, &moor_compiler::ObjVerbDef> = local
686690
.verbs
687691
.iter()
688-
.flat_map(|v| v.names.iter().map(move |name| (name.as_string(), v)))
692+
.filter_map(|v| {
693+
// Use the first name as the identifier for this verb
694+
v.names.first().map(|name| (name.as_string(), v))
695+
})
689696
.collect();
690697

691698
// Find added, modified, and deleted verbs
692-
for (verb_name, local_verb) in &local_verbs {
693-
if let Some(baseline_verb) = baseline_verbs.get(verb_name) {
699+
// We track which baseline verbs have been matched to avoid marking them as deleted
700+
let mut matched_baseline_verbs: HashSet<String> = HashSet::new();
701+
702+
for (first_name, local_verb) in &local_verbs {
703+
// Check if this verb exists in baseline by looking for any matching name
704+
let baseline_match = baseline_verbs.iter().find(|(_, baseline_verb)| {
705+
// Check if any name from baseline_verb matches any name from local_verb
706+
baseline_verb.names.iter().any(|bn|
707+
local_verb.names.iter().any(|ln| bn.as_string() == ln.as_string())
708+
)
709+
});
710+
711+
if let Some((baseline_first_name, baseline_verb)) = baseline_match {
712+
matched_baseline_verbs.insert(baseline_first_name.clone());
713+
694714
// Verb exists in both - check if it's modified
695715
if verbs_differ(baseline_verb, local_verb) {
696-
object_change.verbs_modified.insert(verb_name.clone());
716+
object_change.verbs_modified.insert(first_name.clone());
697717
}
698718
} else {
699-
// Verb is new
700-
object_change.verbs_added.insert(verb_name.clone());
719+
// Verb is new (no matching names in baseline)
720+
object_change.verbs_added.insert(first_name.clone());
701721
}
702722
}
703723

704-
for verb_name in baseline_verbs.keys() {
705-
if !local_verbs.contains_key(verb_name) {
706-
// Verb is missing - check if it's ignored before marking as deleted
707-
let is_ignored = meta
708-
.as_ref()
709-
.map(|m| m.ignored_verbs.contains(verb_name))
710-
.unwrap_or(false);
724+
// Check for deleted verbs (those in baseline but not matched in local)
725+
for (baseline_first_name, baseline_verb) in &baseline_verbs {
726+
if !matched_baseline_verbs.contains(baseline_first_name) {
727+
// Check if any name from this verb is ignored
728+
let is_ignored = meta.as_ref().map(|m| {
729+
baseline_verb.names.iter().any(|name| m.ignored_verbs.contains(&name.as_string()))
730+
}).unwrap_or(false);
711731

712732
if !is_ignored {
713733
// Verb was actually deleted (not just ignored)
714-
object_change.verbs_deleted.insert(verb_name.clone());
734+
object_change.verbs_deleted.insert(baseline_first_name.clone());
715735
} else {
716736
tracing::debug!(
717737
"Verb '{}' is missing but ignored in meta, not marking as deleted",
718-
verb_name
738+
baseline_first_name
719739
);
720740
}
721741
}

vcs-worker/tests/operations/object/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! - rename_workflow_advanced_tests: Advanced rename workflow tests (rename→modify, rename→approve, etc.)
1313
//! - hint_tests: Tests for verb and property rename hints system
1414
//! - switch_tests: Tests for object/switch operation (moving objects between changes)
15+
//! - object_diff_tests: Unit tests for object_diff module (verb counting with multiple names)
1516
1617
mod crud;
1718
mod delete_tests;
@@ -20,6 +21,7 @@ mod hint_tests;
2021
mod history_tests;
2122
mod lifecycle;
2223
mod list;
24+
mod object_diff_tests;
2325
mod rename_edge_cases_tests;
2426
mod rename_modified_object_refs_test;
2527
mod rename_update_integration;
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//! Unit tests for object_diff module
2+
//!
3+
//! These tests verify that verbs with multiple names are counted correctly
4+
//! (as single verbs, not multiple) in ObjectChange tracking.
5+
6+
use moor_vcs_worker::object_diff::{compare_object_definitions_with_meta, ObjectChange};
7+
use moor_compiler::ObjectDefinition;
8+
9+
// Helper function to create a verb definition with multiple names
10+
fn create_verb_def(names: Vec<&str>) -> moor_compiler::ObjVerbDef {
11+
use moor_compiler::ObjVerbDef;
12+
use moor_var::{Symbol, Obj, program::ProgramType};
13+
use moor_common::model::VerbArgsSpec;
14+
use moor_common::util::BitEnum;
15+
16+
ObjVerbDef {
17+
names: names.iter().map(|n| Symbol::mk(n)).collect(),
18+
argspec: VerbArgsSpec::this_none_this(),
19+
owner: Obj::mk_id(1),
20+
flags: BitEnum::new(),
21+
program: ProgramType::MooR(Default::default()),
22+
}
23+
}
24+
25+
// Helper function to create a property definition
26+
fn create_prop_def(name: &str, value: moor_var::Var) -> moor_compiler::ObjPropDef {
27+
use moor_compiler::ObjPropDef;
28+
use moor_var::{Symbol, Obj};
29+
use moor_common::model::{PropPerms, PropFlag};
30+
use moor_common::util::BitEnum;
31+
32+
ObjPropDef {
33+
name: Symbol::mk(name),
34+
perms: PropPerms::new(Obj::mk_id(1), BitEnum::<PropFlag>::new()),
35+
value: Some(value),
36+
}
37+
}
38+
39+
// Helper function to create a basic object definition
40+
fn create_object_def() -> ObjectDefinition {
41+
use moor_var::Obj;
42+
use moor_common::util::BitEnum;
43+
44+
ObjectDefinition {
45+
oid: Obj::mk_id(1),
46+
parent: Obj::mk_id(0),
47+
location: Obj::mk_id(0),
48+
owner: Obj::mk_id(1),
49+
name: "test".to_string(),
50+
flags: BitEnum::new(),
51+
verbs: vec![],
52+
property_definitions: vec![],
53+
property_overrides: vec![],
54+
}
55+
}
56+
57+
#[test]
58+
fn test_verb_with_multiple_names_added() {
59+
// Test that a verb with multiple names is counted as ONE verb added, not multiple
60+
let baseline = create_object_def();
61+
62+
let mut local = create_object_def();
63+
// Add a verb with 3 names: "@foo", "foo", "bar"
64+
local.verbs.push(create_verb_def(vec!["@foo", "foo", "bar"]));
65+
66+
let mut object_change = ObjectChange::new("test".to_string());
67+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
68+
69+
// Should only count as ONE verb added
70+
assert_eq!(object_change.verbs_added.len(), 1,
71+
"Expected 1 verb added, got {}: {:?}",
72+
object_change.verbs_added.len(),
73+
object_change.verbs_added);
74+
75+
// Should be empty
76+
assert_eq!(object_change.verbs_modified.len(), 0);
77+
assert_eq!(object_change.verbs_deleted.len(), 0);
78+
}
79+
80+
#[test]
81+
fn test_verb_with_multiple_names_deleted() {
82+
// Test that a verb with multiple names is counted as ONE verb deleted
83+
let mut baseline = create_object_def();
84+
baseline.verbs.push(create_verb_def(vec!["@foo", "foo", "bar"]));
85+
86+
let local = create_object_def();
87+
88+
let mut object_change = ObjectChange::new("test".to_string());
89+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
90+
91+
// Should only count as ONE verb deleted
92+
assert_eq!(object_change.verbs_deleted.len(), 1,
93+
"Expected 1 verb deleted, got {}: {:?}",
94+
object_change.verbs_deleted.len(),
95+
object_change.verbs_deleted);
96+
97+
assert_eq!(object_change.verbs_added.len(), 0);
98+
assert_eq!(object_change.verbs_modified.len(), 0);
99+
}
100+
101+
#[test]
102+
fn test_verb_with_multiple_names_modified() {
103+
// Test that a verb with multiple names is counted as ONE verb modified
104+
let mut baseline = create_object_def();
105+
baseline.verbs.push(create_verb_def(vec!["@foo", "foo", "bar"]));
106+
107+
let mut local = create_object_def();
108+
let mut modified_verb = create_verb_def(vec!["@foo", "foo", "bar"]);
109+
// Change the owner to make it different
110+
modified_verb.owner = moor_var::Obj::mk_id(2);
111+
local.verbs.push(modified_verb);
112+
113+
let mut object_change = ObjectChange::new("test".to_string());
114+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
115+
116+
// Should only count as ONE verb modified
117+
assert_eq!(object_change.verbs_modified.len(), 1,
118+
"Expected 1 verb modified, got {}: {:?}",
119+
object_change.verbs_modified.len(),
120+
object_change.verbs_modified);
121+
122+
assert_eq!(object_change.verbs_added.len(), 0);
123+
assert_eq!(object_change.verbs_deleted.len(), 0);
124+
}
125+
126+
#[test]
127+
fn test_multiple_verbs_with_multiple_names() {
128+
// Test multiple verbs, each with multiple names
129+
let baseline = create_object_def();
130+
131+
let mut local = create_object_def();
132+
local.verbs.push(create_verb_def(vec!["@foo", "foo", "bar"]));
133+
local.verbs.push(create_verb_def(vec!["@baz", "baz"]));
134+
local.verbs.push(create_verb_def(vec!["single"]));
135+
136+
let mut object_change = ObjectChange::new("test".to_string());
137+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
138+
139+
// Should count as THREE verbs added (not 6)
140+
assert_eq!(object_change.verbs_added.len(), 3,
141+
"Expected 3 verbs added, got {}: {:?}",
142+
object_change.verbs_added.len(),
143+
object_change.verbs_added);
144+
}
145+
146+
#[test]
147+
fn test_mixed_verb_operations() {
148+
// Test a complex scenario with added, modified, and deleted verbs
149+
let mut baseline = create_object_def();
150+
baseline.verbs.push(create_verb_def(vec!["@keep", "keep"]));
151+
baseline.verbs.push(create_verb_def(vec!["@delete", "delete", "del"]));
152+
153+
let mut local = create_object_def();
154+
// Keep one verb but modify it
155+
let mut modified = create_verb_def(vec!["@keep", "keep"]);
156+
modified.owner = moor_var::Obj::mk_id(2);
157+
local.verbs.push(modified);
158+
// Add a new verb
159+
local.verbs.push(create_verb_def(vec!["@new", "new", "n"]));
160+
161+
let mut object_change = ObjectChange::new("test".to_string());
162+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
163+
164+
assert_eq!(object_change.verbs_modified.len(), 1,
165+
"Expected 1 verb modified, got {}", object_change.verbs_modified.len());
166+
assert_eq!(object_change.verbs_added.len(), 1,
167+
"Expected 1 verb added, got {}", object_change.verbs_added.len());
168+
assert_eq!(object_change.verbs_deleted.len(), 1,
169+
"Expected 1 verb deleted, got {}", object_change.verbs_deleted.len());
170+
}
171+
172+
#[test]
173+
fn test_properties_are_not_affected() {
174+
// Verify that properties work correctly (they should, since they have single names)
175+
let baseline = create_object_def();
176+
177+
let mut local = create_object_def();
178+
local.property_definitions.push(create_prop_def("prop1", moor_var::v_int(42)));
179+
local.property_definitions.push(create_prop_def("prop2", moor_var::v_str("test")));
180+
181+
let mut object_change = ObjectChange::new("test".to_string());
182+
compare_object_definitions_with_meta(&baseline, &local, &mut object_change, None, None, None, None);
183+
184+
// Should count as TWO properties added
185+
assert_eq!(object_change.props_added.len(), 2,
186+
"Expected 2 properties added, got {}: {:?}",
187+
object_change.props_added.len(),
188+
object_change.props_added);
189+
}
190+

0 commit comments

Comments
 (0)