wip: update our graph to ensure deterministic ordering
This commit is contained in:
parent
8f2ca44ad7
commit
19fe4cc729
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@ result/
|
|||||||
result
|
result
|
||||||
target/
|
target/
|
||||||
*.avanterules
|
*.avanterules
|
||||||
|
.claude/*
|
||||||
|
.claude
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{cmp::Ordering, collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use blake2::{Blake2b512, Digest};
|
use blake2::{Blake2b512, Digest};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@ -60,9 +60,17 @@ impl Reference {
|
|||||||
pub fn add_dep(&self, dep: Arc<Reference>) -> Self {
|
pub fn add_dep(&self, dep: Arc<Reference>) -> Self {
|
||||||
let mut cloned = self.clone();
|
let mut cloned = self.clone();
|
||||||
cloned.dependents.push(dep);
|
cloned.dependents.push(dep);
|
||||||
|
// We ensure that our dependents are always sorted lexicographically by name.
|
||||||
|
cloned.dependents.sort_by(|left, right| if left.name == right.name {
|
||||||
|
Ordering::Equal
|
||||||
|
} else if left.name < right.name {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Greater
|
||||||
|
});
|
||||||
// Recalculate the ID based on dependents, content_address, and path
|
// Recalculate the ID based on dependents, content_address, and path
|
||||||
let mut hasher = Self::initial_hash(&self.content_address, &self.name);
|
let mut hasher = Self::initial_hash(&self.content_address, &self.name);
|
||||||
for dependent in &self.dependents {
|
for dependent in &cloned.dependents {
|
||||||
hasher.update(&dependent.id);
|
hasher.update(&dependent.id);
|
||||||
}
|
}
|
||||||
cloned.id = format!("{:x}", hasher.finalize());
|
cloned.id = format!("{:x}", hasher.finalize());
|
||||||
|
@ -2,15 +2,20 @@ use std::collections::HashMap;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rand::random_range;
|
|
||||||
|
|
||||||
use crate::{Graph, Reference, random_object};
|
use crate::{Graph, Reference, random_object};
|
||||||
|
|
||||||
fn get_random_candidate(graph: &Graph) -> Arc<Reference> {
|
fn get_deterministic_candidate(graph: &Graph) -> Arc<Reference> {
|
||||||
// Pick a random leaf node to update
|
// Pick a deterministic leaf node to update (first lexicographically)
|
||||||
let refs: Vec<Arc<Reference>> =graph.refs.values().filter(|r| r.name != graph.root.name).map(|r| r.clone()).collect();
|
let mut refs: Vec<Arc<Reference>> = graph.refs.values()
|
||||||
let random_index = random_range(0..refs.len());
|
.filter(|r| r.name != graph.root.name && r.is_leaf())
|
||||||
refs[random_index].clone()
|
.map(|r| r.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Sort by name to ensure deterministic ordering
|
||||||
|
refs.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
|
refs[0].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that all dependencies are kept updated when new nodes are added
|
/// Tests that all dependencies are kept updated when new nodes are added
|
||||||
@ -22,13 +27,14 @@ fn test_dependencies_updated_when_nodes_added() {
|
|||||||
// Get the initial content address of the root
|
// Get the initial content address of the root
|
||||||
let initial_root_id = graph.root.id.clone();
|
let initial_root_id = graph.root.id.clone();
|
||||||
|
|
||||||
let candidate = get_random_candidate(&graph);
|
let candidate = get_deterministic_candidate(&graph);
|
||||||
|
|
||||||
// Update the leaf node
|
// Update the leaf node with deterministic content
|
||||||
graph.update_object_reference(&candidate.name, random_object().1).unwrap();
|
let new_content = b"deterministic_test_content".to_vec();
|
||||||
|
graph.update_object_reference(&candidate.name, new_content).unwrap();
|
||||||
|
|
||||||
// Verify that the leaf node's ID has changed
|
// Verify that the leaf node's ID has changed
|
||||||
let updated_leaf = dbg!(graph.get_reference(&candidate.name).unwrap());
|
let updated_leaf = graph.get_reference(&candidate.name).unwrap();
|
||||||
assert_ne!(updated_leaf.id, candidate.id,
|
assert_ne!(updated_leaf.id, candidate.id,
|
||||||
"Leaf node ID should change when content is updated");
|
"Leaf node ID should change when content is updated");
|
||||||
|
|
||||||
@ -200,3 +206,90 @@ fn test_id_calculation() {
|
|||||||
"ID calculation should be consistent for the same reference properties");
|
"ID calculation should be consistent for the same reference properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that the root ID always changes when any reference is updated
|
||||||
|
#[test]
|
||||||
|
fn test_root_id_changes_for_any_reference_update() {
|
||||||
|
let mut graph = create_test_graph();
|
||||||
|
|
||||||
|
// Get all non-root references sorted by name for deterministic iteration
|
||||||
|
let mut all_refs: Vec<(String, String)> = graph.refs.as_ref()
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| **name != graph.root.name)
|
||||||
|
.map(|(name, ref_arc)| (name.clone(), ref_arc.id.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
all_refs.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
|
// Test each reference update
|
||||||
|
for (ref_name, original_ref_id) in all_refs {
|
||||||
|
// Record the current root ID
|
||||||
|
let initial_root_id = graph.root.id.clone();
|
||||||
|
|
||||||
|
// Update the reference with new content
|
||||||
|
let new_content = format!("updated_content_for_{}", ref_name).into_bytes();
|
||||||
|
graph.update_object_reference(&ref_name, new_content).unwrap();
|
||||||
|
|
||||||
|
// Verify the reference itself changed
|
||||||
|
let updated_ref = graph.get_reference(&ref_name).unwrap();
|
||||||
|
assert_ne!(updated_ref.id, original_ref_id,
|
||||||
|
"Reference {} should have changed ID after update", ref_name);
|
||||||
|
|
||||||
|
// Verify the root ID changed
|
||||||
|
assert_ne!(graph.root.id, initial_root_id,
|
||||||
|
"Root ID should change when reference {} is updated", ref_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that dependencies of a Reference are always lexicographically ordered by name
|
||||||
|
#[test]
|
||||||
|
fn test_dependencies_lexicographically_ordered() {
|
||||||
|
let graph = create_test_graph();
|
||||||
|
|
||||||
|
// Check all references to ensure their dependents are lexicographically ordered
|
||||||
|
for (_, reference) in graph.refs.as_ref() {
|
||||||
|
if reference.dependents.len() > 1 {
|
||||||
|
// Check that dependents are ordered by name
|
||||||
|
for i in 0..reference.dependents.len() - 1 {
|
||||||
|
let current = &reference.dependents[i];
|
||||||
|
let next = &reference.dependents[i + 1];
|
||||||
|
|
||||||
|
assert!(current.name < next.name,
|
||||||
|
"Dependencies should be lexicographically ordered by name. Found '{}' before '{}'",
|
||||||
|
current.name, next.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also verify that when we add dependencies, they maintain the correct order
|
||||||
|
let mut test_ref = Reference::new(
|
||||||
|
Some(String::from("test_content")),
|
||||||
|
String::from("/test_ordering"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add dependencies in non-lexicographical order
|
||||||
|
let dep_c = Reference::new(
|
||||||
|
Some(String::from("c_content")),
|
||||||
|
String::from("/c"),
|
||||||
|
).to_arc();
|
||||||
|
|
||||||
|
let dep_a = Reference::new(
|
||||||
|
Some(String::from("a_content")),
|
||||||
|
String::from("/a"),
|
||||||
|
).to_arc();
|
||||||
|
|
||||||
|
let dep_b = Reference::new(
|
||||||
|
Some(String::from("b_content")),
|
||||||
|
String::from("/b"),
|
||||||
|
).to_arc();
|
||||||
|
|
||||||
|
// Add in non-lexicographical order
|
||||||
|
test_ref = test_ref.add_dep(dep_c.clone());
|
||||||
|
test_ref = test_ref.add_dep(dep_a.clone());
|
||||||
|
test_ref = test_ref.add_dep(dep_b.clone());
|
||||||
|
|
||||||
|
// Verify they are stored in lexicographical order
|
||||||
|
assert_eq!(test_ref.dependents[0].name, "/a", "First dependent should be '/a'");
|
||||||
|
assert_eq!(test_ref.dependents[1].name, "/b", "Second dependent should be '/b'");
|
||||||
|
assert_eq!(test_ref.dependents[2].name, "/c", "Third dependent should be '/c'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user