chore: cargo clippy fixes
This commit is contained in:
parent
a73b5ac597
commit
78f114254c
@ -1,6 +1,12 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use axum::{extract::Path, http, response::{Html, IntoResponse}, routing::get, Json, Router};
|
use axum::{
|
||||||
|
extract::Path,
|
||||||
|
http,
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::get,
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
|
||||||
use offline_web_model::{Graph, Reference};
|
use offline_web_model::{Graph, Reference};
|
||||||
|
|
||||||
@ -9,7 +15,10 @@ async fn all_references(root_ref: Arc<Reference>) -> Json<Arc<Reference>> {
|
|||||||
Json(root_ref)
|
Json(root_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ref_path(refs: Arc<HashMap<String, Arc<Reference>>>, Path(path): Path<String>) -> Json<Arc<Reference>> {
|
async fn ref_path(
|
||||||
|
refs: Arc<HashMap<String, Arc<Reference>>>,
|
||||||
|
Path(path): Path<String>,
|
||||||
|
) -> Json<Arc<Reference>> {
|
||||||
let path = format!("/item/{}", path);
|
let path = format!("/item/{}", path);
|
||||||
match refs.get(&path) {
|
match refs.get(&path) {
|
||||||
Some(r) => Json(r.clone()),
|
Some(r) => Json(r.clone()),
|
||||||
@ -33,37 +42,51 @@ async fn get_client_js() -> impl IntoResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn endpoints(graph: Graph) -> Router {
|
pub fn endpoints(graph: Graph) -> Router {
|
||||||
Router::new().nest(
|
Router::new()
|
||||||
"/api/v1",
|
.nest(
|
||||||
Router::new().nest(
|
"/api/v1",
|
||||||
"/ref",
|
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/all/username", get({
|
.nest(
|
||||||
let state = graph.root.clone();
|
"/ref",
|
||||||
move || all_references(state)
|
Router::new()
|
||||||
}))
|
.route(
|
||||||
.route("/item/{*path}", get({
|
"/all/username",
|
||||||
let refs = graph.refs.clone();
|
get({
|
||||||
move |path| ref_path(refs, path)
|
let state = graph.root.clone();
|
||||||
}))
|
move || all_references(state)
|
||||||
).nest(
|
}),
|
||||||
"/object",
|
)
|
||||||
Router::new()
|
.route(
|
||||||
.route("/{addr}", get({
|
"/item/{*path}",
|
||||||
let objects = graph.objects.clone();
|
get({
|
||||||
move |addr| object_path(objects, addr)
|
let refs = graph.refs.clone();
|
||||||
}))
|
move |path| ref_path(refs, path)
|
||||||
),
|
}),
|
||||||
)
|
),
|
||||||
.route("/lib/client.js", get(get_client_js))
|
)
|
||||||
.route("/ui/", get(|| async { Html(include_str!("../static/index.html")).into_response() }))
|
.nest(
|
||||||
|
"/object",
|
||||||
|
Router::new().route(
|
||||||
|
"/{addr}",
|
||||||
|
get({
|
||||||
|
let objects = graph.objects.clone();
|
||||||
|
move |addr| object_path(objects, addr)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.route("/lib/client.js", get(get_client_js))
|
||||||
|
.route(
|
||||||
|
"/ui/",
|
||||||
|
get(|| async { Html(include_str!("../static/index.html")).into_response() }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jwall): Javascript test script
|
// TODO(jwall): Javascript test script
|
||||||
pub async fn serve() {
|
pub async fn serve() {
|
||||||
// run our app with hyper, listening globally on port 3000
|
// run our app with hyper, listening globally on port 3000
|
||||||
let graph = Graph::random_graph();
|
let graph = Graph::random_graph();
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
mod serve;
|
mod serve;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{cmp::Ordering, collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use blake2::{Blake2b512, Digest};
|
use blake2::{Blake2b512, Digest};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@ -37,7 +37,7 @@ impl Reference {
|
|||||||
// Calculate the reference_id from the content_address and path
|
// Calculate the reference_id from the content_address and path
|
||||||
let hasher = Self::initial_hash(&content_address, &name);
|
let hasher = Self::initial_hash(&content_address, &name);
|
||||||
let calculated_id = format!("{:x}", hasher.finalize());
|
let calculated_id = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id: calculated_id,
|
id: calculated_id,
|
||||||
content_address,
|
content_address,
|
||||||
@ -61,12 +61,8 @@ impl Reference {
|
|||||||
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.
|
// We ensure that our dependents are always sorted lexicographically by name.
|
||||||
cloned.dependents.sort_by(|left, right| if left.name == right.name {
|
cloned.dependents.sort_by(|left, right| {
|
||||||
Ordering::Equal
|
left.name.cmp(&right.name)
|
||||||
} 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);
|
||||||
@ -77,7 +73,6 @@ impl Reference {
|
|||||||
cloned
|
cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Converts this Reference into an Arc-wrapped Reference for shared ownership.
|
/// Converts this Reference into an Arc-wrapped Reference for shared ownership.
|
||||||
///
|
///
|
||||||
/// This is useful when a Reference needs to be shared across multiple owners
|
/// This is useful when a Reference needs to be shared across multiple owners
|
||||||
@ -97,9 +92,9 @@ impl Reference {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// `true` if this Reference has no dependents, `false` otherwise
|
/// `true` if this Reference has no dependents, `false` otherwise
|
||||||
pub fn is_leaf(&self) -> bool {
|
pub fn is_leaf(&self) -> bool {
|
||||||
return self.dependents.is_empty();
|
self.dependents.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_hash(content_address: &Option<String>, path: &String) -> Blake2b512 {
|
fn initial_hash(content_address: &Option<String>, path: &String) -> Blake2b512 {
|
||||||
let mut hasher = Blake2b512::new();
|
let mut hasher = Blake2b512::new();
|
||||||
if let Some(content_address) = content_address {
|
if let Some(content_address) = content_address {
|
||||||
@ -119,9 +114,7 @@ pub struct Graph {
|
|||||||
pub fn random_object() -> (String, Vec<u8>) {
|
pub fn random_object() -> (String, Vec<u8>) {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
let random_size = rng.random_range(50..=4096);
|
let random_size = rng.random_range(50..=4096);
|
||||||
let random_bytes: Vec<u8> = (0..random_size)
|
let random_bytes: Vec<u8> = (0..random_size).map(|_| rng.random::<u8>()).collect();
|
||||||
.map(|_| rng.random::<u8>())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut hasher = Blake2b512::new();
|
let mut hasher = Blake2b512::new();
|
||||||
hasher.update(&random_bytes);
|
hasher.update(&random_bytes);
|
||||||
@ -147,7 +140,7 @@ impl Graph {
|
|||||||
pub fn get_reference(&self, path: &str) -> Option<Arc<Reference>> {
|
pub fn get_reference(&self, path: &str) -> Option<Arc<Reference>> {
|
||||||
self.refs.get(path).cloned()
|
self.refs.get(path).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets an object by its content address
|
/// Gets an object by its content address
|
||||||
pub fn get_object(&self, content_address: &str) -> Option<&Vec<u8>> {
|
pub fn get_object(&self, content_address: &str) -> Option<&Vec<u8>> {
|
||||||
self.objects.get(content_address)
|
self.objects.get(content_address)
|
||||||
@ -158,97 +151,101 @@ impl Graph {
|
|||||||
///
|
///
|
||||||
/// The reference ID is calculated from the content address, name, and any dependents,
|
/// The reference ID is calculated from the content address, name, and any dependents,
|
||||||
/// ensuring that it's truly content-addressable.
|
/// ensuring that it's truly content-addressable.
|
||||||
pub fn update_object_reference(&mut self, name: &String, new_content: Vec<u8>) -> Result<(), String> {
|
pub fn update_object_reference(
|
||||||
|
&mut self,
|
||||||
|
name: &String,
|
||||||
|
new_content: Vec<u8>,
|
||||||
|
) -> Result<(), String> {
|
||||||
// Create a mutable copy of our maps
|
// Create a mutable copy of our maps
|
||||||
let mut refs = HashMap::new();
|
let mut refs = HashMap::new();
|
||||||
for (k, v) in self.refs.as_ref() {
|
for (k, v) in self.refs.as_ref() {
|
||||||
refs.insert(k.clone(), v.clone());
|
refs.insert(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut objects = HashMap::new();
|
let mut objects = HashMap::new();
|
||||||
for (k, v) in self.objects.as_ref() {
|
for (k, v) in self.objects.as_ref() {
|
||||||
objects.insert(k.clone(), v.clone());
|
objects.insert(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the reference to update
|
// Find the reference to update
|
||||||
let ref_to_update = refs.get(name).ok_or_else(|| format!("Reference with name {} not found", name))?;
|
let ref_to_update = refs
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| format!("Reference with name {} not found", name))?;
|
||||||
|
|
||||||
// Calculate hash for the new content
|
// Calculate hash for the new content
|
||||||
let mut hasher = Blake2b512::new();
|
let mut hasher = Blake2b512::new();
|
||||||
hasher.update(&new_content);
|
hasher.update(&new_content);
|
||||||
let new_address = format!("{:x}", hasher.finalize());
|
let new_address = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
// Create a new reference with the updated content address
|
// Create a new reference with the updated content address
|
||||||
// The ID will be calculated based on the content address, name, and dependents
|
// The ID will be calculated based on the content address, name, and dependents
|
||||||
let mut updated_ref = Reference::new(
|
let mut updated_ref = Reference::new(Some(new_address.clone()), name.to_string());
|
||||||
Some(new_address.clone()),
|
|
||||||
name.to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add all dependents to the updated reference
|
// Add all dependents to the updated reference
|
||||||
for dep in &ref_to_update.dependents {
|
for dep in &ref_to_update.dependents {
|
||||||
updated_ref = updated_ref.add_dep(dep.clone());
|
updated_ref = updated_ref.add_dep(dep.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let updated_ref = updated_ref.to_arc();
|
let updated_ref = updated_ref.to_arc();
|
||||||
|
|
||||||
// Update objects map with new content
|
// Update objects map with new content
|
||||||
objects.insert(new_address.clone(), new_content);
|
objects.insert(new_address.clone(), new_content);
|
||||||
|
|
||||||
// Update references map with new reference
|
// Update references map with new reference
|
||||||
refs.insert(name.to_string(), updated_ref.clone());
|
refs.insert(name.to_string(), updated_ref.clone());
|
||||||
|
|
||||||
// Find and update all parent references that contain this reference
|
// Find and update all parent references that contain this reference
|
||||||
self.update_parent_references(&mut refs, name);
|
self.update_parent_references(&mut refs, name);
|
||||||
|
|
||||||
// Update the Arc maps
|
// Update the Arc maps
|
||||||
self.refs = Arc::new(refs);
|
self.refs = Arc::new(refs);
|
||||||
self.objects = Arc::new(objects);
|
self.objects = Arc::new(objects);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO(jwall): Add new reference
|
/// TODO(jwall): Add new reference
|
||||||
|
|
||||||
/// Recursively updates parent references when a child reference changes
|
/// Recursively updates parent references when a child reference changes
|
||||||
fn update_parent_references(&mut self, refs: &mut HashMap<String, Arc<Reference>>, updated_name: &str) {
|
fn update_parent_references(
|
||||||
|
&mut self,
|
||||||
|
refs: &mut HashMap<String, Arc<Reference>>,
|
||||||
|
updated_name: &str,
|
||||||
|
) {
|
||||||
// Find all references that have the updated reference as a dependent
|
// Find all references that have the updated reference as a dependent
|
||||||
let parent_names: Vec<String> = refs
|
let parent_names: Vec<String> = refs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, r)| r.dependents.iter().any(|dep| dep.name == updated_name))
|
.filter(|(_, r)| r.dependents.iter().any(|dep| dep.name == updated_name))
|
||||||
.map(|(name, _)| name.clone())
|
.map(|(name, _)| name.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for parent_name in parent_names {
|
for parent_name in parent_names {
|
||||||
if let Some(parent_ref) = refs.get(&parent_name) {
|
if let Some(parent_ref) = refs.get(&parent_name) {
|
||||||
// Create a new reference with the same content address and name
|
// Create a new reference with the same content address and name
|
||||||
let mut updated_parent = Reference::new(
|
let mut updated_parent =
|
||||||
parent_ref.content_address.clone(),
|
Reference::new(parent_ref.content_address.clone(), parent_ref.name.clone());
|
||||||
parent_ref.name.clone()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add all dependents, replacing the updated one
|
// Add all dependents, replacing the updated one
|
||||||
for dep in &parent_ref.dependents {
|
for dep in &parent_ref.dependents {
|
||||||
// Update the root reference if needed
|
// Update the root reference if needed
|
||||||
if dep.name == updated_name {
|
if dep.name == updated_name {
|
||||||
// Use the updated reference
|
// Use the updated reference
|
||||||
updated_parent = updated_parent.add_dep(refs.get(updated_name).unwrap().clone());
|
updated_parent =
|
||||||
|
updated_parent.add_dep(refs.get(updated_name).unwrap().clone());
|
||||||
} else {
|
} else {
|
||||||
// Keep the existing dependent
|
// Keep the existing dependent
|
||||||
updated_parent = updated_parent.add_dep(dep.clone());
|
updated_parent = updated_parent.add_dep(dep.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ID is automatically calculated in the add_dep method
|
// The ID is automatically calculated in the add_dep method
|
||||||
let updated_parent = Arc::new(updated_parent);
|
let updated_parent = Arc::new(updated_parent);
|
||||||
if updated_parent.name == self.root.name {
|
if updated_parent.name == self.root.name {
|
||||||
self.root = updated_parent.clone();
|
self.root = updated_parent.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the references map
|
// Update the references map
|
||||||
refs.insert(parent_name.clone(), updated_parent);
|
refs.insert(parent_name.clone(), updated_parent);
|
||||||
|
|
||||||
// Recursively update parents of this parent
|
// Recursively update parents of this parent
|
||||||
self.update_parent_references(refs, &parent_name);
|
self.update_parent_references(refs, &parent_name);
|
||||||
}
|
}
|
||||||
@ -259,56 +256,48 @@ impl Graph {
|
|||||||
let root_name = String::from("ref/0");
|
let root_name = String::from("ref/0");
|
||||||
let mut objects = HashMap::new();
|
let mut objects = HashMap::new();
|
||||||
let mut refs = HashMap::new();
|
let mut refs = HashMap::new();
|
||||||
|
|
||||||
// Create the root reference
|
// Create the root reference
|
||||||
let mut root_ref = Reference::new(
|
let mut root_ref = Reference::new(Some(String::from("root_content")), root_name.clone());
|
||||||
Some(String::from("root_content")),
|
|
||||||
root_name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create 10 item references
|
// Create 10 item references
|
||||||
for i in 1..=10 {
|
for i in 1..=10 {
|
||||||
let item_name = format!("/item/{}", i);
|
let item_name = format!("/item/{}", i);
|
||||||
let mut item_ref = Reference::new(
|
let mut item_ref =
|
||||||
Some(format!("item_content_{}", i)),
|
Reference::new(Some(format!("item_content_{}", i)), item_name.clone());
|
||||||
item_name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create 10 subitems for each item
|
// Create 10 subitems for each item
|
||||||
for j in 1..=10 {
|
for j in 1..=10 {
|
||||||
let (address, content) = random_object();
|
let (address, content) = random_object();
|
||||||
let subitem_name = format!("/item/{}/subitem/{}", i, j);
|
let subitem_name = format!("/item/{}/subitem/{}", i, j);
|
||||||
|
|
||||||
// Create a leaf reference
|
// Create a leaf reference
|
||||||
let leaf_ref = Reference::new(
|
let leaf_ref = Reference::new(Some(address.clone()), subitem_name).to_arc();
|
||||||
Some(address.clone()),
|
|
||||||
subitem_name,
|
|
||||||
).to_arc();
|
|
||||||
|
|
||||||
// Add the leaf reference as a dependent to the item reference
|
// Add the leaf reference as a dependent to the item reference
|
||||||
item_ref = item_ref.add_dep(leaf_ref.clone());
|
item_ref = item_ref.add_dep(leaf_ref.clone());
|
||||||
|
|
||||||
// Store the content in the objects map
|
// Store the content in the objects map
|
||||||
objects.insert(address.clone(), content);
|
objects.insert(address.clone(), content);
|
||||||
|
|
||||||
// Store the leaf reference in the refs map
|
// Store the leaf reference in the refs map
|
||||||
refs.insert(leaf_ref.name.clone(), leaf_ref);
|
refs.insert(leaf_ref.name.clone(), leaf_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the item reference to Arc and add it to the root reference
|
// Convert the item reference to Arc and add it to the root reference
|
||||||
let arc_item_ref = item_ref.to_arc();
|
let arc_item_ref = item_ref.to_arc();
|
||||||
root_ref = root_ref.add_dep(arc_item_ref.clone());
|
root_ref = root_ref.add_dep(arc_item_ref.clone());
|
||||||
|
|
||||||
// Store the item reference in the refs map
|
// Store the item reference in the refs map
|
||||||
refs.insert(arc_item_ref.name.clone(), arc_item_ref);
|
refs.insert(arc_item_ref.name.clone(), arc_item_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the root reference to Arc
|
// Convert the root reference to Arc
|
||||||
let arc_root_ref = root_ref.to_arc();
|
let arc_root_ref = root_ref.to_arc();
|
||||||
|
|
||||||
// Store the root reference in the refs map
|
// Store the root reference in the refs map
|
||||||
refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone());
|
refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone());
|
||||||
|
|
||||||
Graph {
|
Graph {
|
||||||
root: arc_root_ref,
|
root: arc_root_ref,
|
||||||
refs: Arc::new(refs),
|
refs: Arc::new(refs),
|
||||||
|
@ -2,19 +2,20 @@ use std::collections::HashMap;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
||||||
use crate::{Graph, Reference, random_object};
|
use crate::{Graph, Reference, random_object};
|
||||||
|
|
||||||
fn get_deterministic_candidate(graph: &Graph) -> Arc<Reference> {
|
fn get_deterministic_candidate(graph: &Graph) -> Arc<Reference> {
|
||||||
// Pick a deterministic leaf node to update (first lexicographically)
|
// Pick a deterministic leaf node to update (first lexicographically)
|
||||||
let mut refs: Vec<Arc<Reference>> = graph.refs.values()
|
let mut refs: Vec<Arc<Reference>> = graph
|
||||||
|
.refs
|
||||||
|
.values()
|
||||||
.filter(|r| r.name != graph.root.name && r.is_leaf())
|
.filter(|r| r.name != graph.root.name && r.is_leaf())
|
||||||
.map(|r| r.clone())
|
.map(|r| r.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by name to ensure deterministic ordering
|
// Sort by name to ensure deterministic ordering
|
||||||
refs.sort_by(|a, b| a.name.cmp(&b.name));
|
refs.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
refs[0].clone()
|
refs[0].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,25 +24,30 @@ fn get_deterministic_candidate(graph: &Graph) -> Arc<Reference> {
|
|||||||
fn test_dependencies_updated_when_nodes_added() {
|
fn test_dependencies_updated_when_nodes_added() {
|
||||||
// Create a simple graph
|
// Create a simple graph
|
||||||
let mut graph = create_test_graph();
|
let mut graph = create_test_graph();
|
||||||
|
|
||||||
// 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_deterministic_candidate(&graph);
|
let candidate = get_deterministic_candidate(&graph);
|
||||||
|
|
||||||
// Update the leaf node with deterministic content
|
// Update the leaf node with deterministic content
|
||||||
let new_content = b"deterministic_test_content".to_vec();
|
let new_content = b"deterministic_test_content".to_vec();
|
||||||
graph.update_object_reference(&candidate.name, new_content).unwrap();
|
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 = graph.get_reference(&candidate.name).unwrap();
|
let updated_leaf = graph.get_reference(&candidate.name).unwrap();
|
||||||
assert_ne!(updated_leaf.id, candidate.id,
|
assert_ne!(
|
||||||
"Leaf node ID should change when content is updated");
|
updated_leaf.id, candidate.id,
|
||||||
|
"Leaf node ID should change when content is updated"
|
||||||
|
);
|
||||||
|
|
||||||
// Verify that the root's ID has changed
|
// Verify that the root's ID has changed
|
||||||
assert_ne!(graph.root.id, initial_root_id,
|
assert_ne!(
|
||||||
"Root ID should change when a dependent node is updated");
|
graph.root.id, initial_root_id,
|
||||||
|
"Root ID should change when a dependent node is updated"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that the root of the graph is not itself a dependency of any other node
|
/// Tests that the root of the graph is not itself a dependency of any other node
|
||||||
@ -49,12 +55,14 @@ fn test_dependencies_updated_when_nodes_added() {
|
|||||||
fn test_root_not_a_dependency() {
|
fn test_root_not_a_dependency() {
|
||||||
let graph = create_test_graph();
|
let graph = create_test_graph();
|
||||||
let root_name = graph.root.name.clone();
|
let root_name = graph.root.name.clone();
|
||||||
|
|
||||||
// Check all references to ensure none have the root as a dependent
|
// Check all references to ensure none have the root as a dependent
|
||||||
for (_, reference) in graph.refs.as_ref() {
|
for (_, reference) in graph.refs.as_ref() {
|
||||||
for dep in &reference.dependents {
|
for dep in &reference.dependents {
|
||||||
assert_ne!(dep.name, root_name,
|
assert_ne!(
|
||||||
"Root should not be a dependency of any other node");
|
dep.name, root_name,
|
||||||
|
"Root should not be a dependency of any other node"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,26 +71,29 @@ fn test_root_not_a_dependency() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_all_nodes_connected_to_root() {
|
fn test_all_nodes_connected_to_root() {
|
||||||
let graph = create_test_graph();
|
let graph = create_test_graph();
|
||||||
|
|
||||||
// Collect all nodes reachable from the root
|
// Collect all nodes reachable from the root
|
||||||
let mut reachable = HashSet::new();
|
let mut reachable = HashSet::new();
|
||||||
|
|
||||||
fn collect_reachable(node: &Arc<Reference>, reachable: &mut HashSet<String>) {
|
fn collect_reachable(node: &Arc<Reference>, reachable: &mut HashSet<String>) {
|
||||||
reachable.insert(node.name.clone());
|
reachable.insert(node.name.clone());
|
||||||
|
|
||||||
for dep in &node.dependents {
|
for dep in &node.dependents {
|
||||||
if !reachable.contains(&dep.name) {
|
if !reachable.contains(&dep.name) {
|
||||||
collect_reachable(dep, reachable);
|
collect_reachable(dep, reachable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_reachable(&graph.root, &mut reachable);
|
collect_reachable(&graph.root, &mut reachable);
|
||||||
|
|
||||||
// Check that all nodes in the graph are reachable from the root
|
// Check that all nodes in the graph are reachable from the root
|
||||||
for (name, _) in graph.refs.as_ref() {
|
for (name, _) in graph.refs.as_ref() {
|
||||||
assert!(reachable.contains(name),
|
assert!(
|
||||||
"All nodes should be reachable from the root: {}", name);
|
reachable.contains(name),
|
||||||
|
"All nodes should be reachable from the root: {}",
|
||||||
|
name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,56 +102,47 @@ fn create_test_graph() -> Graph {
|
|||||||
let root_name = String::from("/root");
|
let root_name = String::from("/root");
|
||||||
let mut objects = HashMap::new();
|
let mut objects = HashMap::new();
|
||||||
let mut refs = HashMap::new();
|
let mut refs = HashMap::new();
|
||||||
|
|
||||||
// Create the root reference
|
// Create the root reference
|
||||||
let mut root_ref = Reference::new(
|
let mut root_ref = Reference::new(Some(String::from("root_content")), root_name.clone());
|
||||||
Some(String::from("root_content")),
|
|
||||||
root_name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create 3 item references
|
// Create 3 item references
|
||||||
for i in 1..=3 {
|
for i in 1..=3 {
|
||||||
let item_name = format!("/item/{}", i);
|
let item_name = format!("/item/{}", i);
|
||||||
let mut item_ref = Reference::new(
|
let mut item_ref = Reference::new(Some(format!("item_content_{}", i)), item_name.clone());
|
||||||
Some(format!("item_content_{}", i)),
|
|
||||||
item_name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create 3 subitems for each item
|
// Create 3 subitems for each item
|
||||||
for j in 1..=3 {
|
for j in 1..=3 {
|
||||||
let (address, content) = random_object();
|
let (address, content) = random_object();
|
||||||
let subitem_name = format!("/item/{}/subitem/{}", i, j);
|
let subitem_name = format!("/item/{}/subitem/{}", i, j);
|
||||||
|
|
||||||
// Create a leaf reference
|
// Create a leaf reference
|
||||||
let leaf_ref = Reference::new(
|
let leaf_ref = Reference::new(Some(address.clone()), subitem_name).to_arc();
|
||||||
Some(address.clone()),
|
|
||||||
subitem_name,
|
|
||||||
).to_arc();
|
|
||||||
|
|
||||||
// Add the leaf reference as a dependent to the item reference
|
// Add the leaf reference as a dependent to the item reference
|
||||||
item_ref = item_ref.add_dep(leaf_ref.clone());
|
item_ref = item_ref.add_dep(leaf_ref.clone());
|
||||||
|
|
||||||
// Store the content in the objects map
|
// Store the content in the objects map
|
||||||
objects.insert(address.clone(), content);
|
objects.insert(address.clone(), content);
|
||||||
|
|
||||||
// Store the leaf reference in the refs map
|
// Store the leaf reference in the refs map
|
||||||
refs.insert(leaf_ref.name.clone(), leaf_ref);
|
refs.insert(leaf_ref.name.clone(), leaf_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the item reference to Arc and add it to the root reference
|
// Convert the item reference to Arc and add it to the root reference
|
||||||
let arc_item_ref = item_ref.to_arc();
|
let arc_item_ref = item_ref.to_arc();
|
||||||
root_ref = root_ref.add_dep(arc_item_ref.clone());
|
root_ref = root_ref.add_dep(arc_item_ref.clone());
|
||||||
|
|
||||||
// Store the item reference in the refs map
|
// Store the item reference in the refs map
|
||||||
refs.insert(arc_item_ref.name.clone(), arc_item_ref);
|
refs.insert(arc_item_ref.name.clone(), arc_item_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the root reference to Arc
|
// Convert the root reference to Arc
|
||||||
let arc_root_ref = root_ref.to_arc();
|
let arc_root_ref = root_ref.to_arc();
|
||||||
|
|
||||||
// Store the root reference in the refs map
|
// Store the root reference in the refs map
|
||||||
refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone());
|
refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone());
|
||||||
|
|
||||||
Graph {
|
Graph {
|
||||||
root: arc_root_ref,
|
root: arc_root_ref,
|
||||||
refs: Arc::new(refs),
|
refs: Arc::new(refs),
|
||||||
@ -152,91 +154,106 @@ fn create_test_graph() -> Graph {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_content_addressable_properties() {
|
fn test_content_addressable_properties() {
|
||||||
let mut graph = create_test_graph();
|
let mut graph = create_test_graph();
|
||||||
|
|
||||||
// Update a leaf node with the same content
|
// Update a leaf node with the same content
|
||||||
let leaf_path = "/item/1/subitem/1".to_string();
|
let leaf_path = "/item/1/subitem/1".to_string();
|
||||||
let initial_leaf = graph.get_reference(&leaf_path).unwrap();
|
let initial_leaf = graph.get_reference(&leaf_path).unwrap();
|
||||||
if let Some(content_address) = initial_leaf.content_address.clone() {
|
if let Some(content_address) = initial_leaf.content_address.clone() {
|
||||||
// Get the content for this address
|
// Get the content for this address
|
||||||
let content = graph.get_object(&content_address).unwrap().clone();
|
let content = graph.get_object(&content_address).unwrap().clone();
|
||||||
|
|
||||||
// Update with the same content
|
// Update with the same content
|
||||||
graph.update_object_reference(&leaf_path, content).unwrap();
|
graph.update_object_reference(&leaf_path, content).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that nothing changed since the content is the same
|
// Verify that nothing changed since the content is the same
|
||||||
let updated_leaf = graph.get_reference(&leaf_path).unwrap();
|
let updated_leaf = graph.get_reference(&leaf_path).unwrap();
|
||||||
assert_eq!(updated_leaf.content_address, initial_leaf.content_address,
|
assert_eq!(
|
||||||
"Content address should not change when content remains the same");
|
updated_leaf.content_address, initial_leaf.content_address,
|
||||||
|
"Content address should not change when content remains the same"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that the graph correctly handles ID calculation
|
/// Tests that the graph correctly handles ID calculation
|
||||||
#[test]
|
#[test]
|
||||||
fn test_id_calculation() {
|
fn test_id_calculation() {
|
||||||
let mut graph = create_test_graph();
|
let mut graph = create_test_graph();
|
||||||
|
|
||||||
// Update a leaf node
|
// Update a leaf node
|
||||||
let leaf_path = "/item/1/subitem/1".to_string();
|
let leaf_path = "/item/1/subitem/1".to_string();
|
||||||
let initial_leaf = graph.get_reference(&leaf_path).unwrap();
|
let initial_leaf = graph.get_reference(&leaf_path).unwrap();
|
||||||
|
|
||||||
graph.update_object_reference(&leaf_path, "new content".as_bytes().to_vec()).unwrap();
|
graph
|
||||||
|
.update_object_reference(&leaf_path, "new content".as_bytes().to_vec())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Verify that the ID changed
|
// Verify that the ID changed
|
||||||
let updated_leaf = graph.get_reference(&leaf_path).unwrap();
|
let updated_leaf = graph.get_reference(&leaf_path).unwrap();
|
||||||
assert_ne!(updated_leaf.id, initial_leaf.id,
|
assert_ne!(
|
||||||
"Reference ID should change when content changes");
|
updated_leaf.id, initial_leaf.id,
|
||||||
|
"Reference ID should change when content changes"
|
||||||
|
);
|
||||||
|
|
||||||
// Verify that parent ID changed
|
// Verify that parent ID changed
|
||||||
let parent_path = "/item/1".to_string();
|
let parent_path = "/item/1".to_string();
|
||||||
let parent = graph.get_reference(&parent_path).unwrap();
|
let parent = graph.get_reference(&parent_path).unwrap();
|
||||||
|
|
||||||
// Create a reference with the same properties to calculate expected ID
|
// Create a reference with the same properties to calculate expected ID
|
||||||
let mut test_ref = Reference::new(
|
let mut test_ref = Reference::new(parent.content_address.clone(), parent.name.clone());
|
||||||
parent.content_address.clone(),
|
|
||||||
parent.name.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the same dependents
|
// Add the same dependents
|
||||||
for dep in &parent.dependents {
|
for dep in &parent.dependents {
|
||||||
test_ref = test_ref.add_dep(dep.clone());
|
test_ref = test_ref.add_dep(dep.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the ID calculation is consistent
|
// Verify the ID calculation is consistent
|
||||||
assert_eq!(parent.id, test_ref.id,
|
assert_eq!(
|
||||||
"ID calculation should be consistent for the same reference properties");
|
parent.id, test_ref.id,
|
||||||
|
"ID calculation should be consistent for the same reference properties"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that the root ID always changes when any reference is updated
|
/// Tests that the root ID always changes when any reference is updated
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_id_changes_for_any_reference_update() {
|
fn test_root_id_changes_for_any_reference_update() {
|
||||||
let mut graph = create_test_graph();
|
let mut graph = create_test_graph();
|
||||||
|
|
||||||
// Get all non-root references sorted by name for deterministic iteration
|
// Get all non-root references sorted by name for deterministic iteration
|
||||||
let mut all_refs: Vec<(String, String)> = graph.refs.as_ref()
|
let mut all_refs: Vec<(String, String)> = graph
|
||||||
|
.refs
|
||||||
|
.as_ref()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(name, _)| **name != graph.root.name)
|
.filter(|(name, _)| **name != graph.root.name)
|
||||||
.map(|(name, ref_arc)| (name.clone(), ref_arc.id.clone()))
|
.map(|(name, ref_arc)| (name.clone(), ref_arc.id.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
all_refs.sort_by(|a, b| a.0.cmp(&b.0));
|
all_refs.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
// Test each reference update
|
// Test each reference update
|
||||||
for (ref_name, original_ref_id) in all_refs {
|
for (ref_name, original_ref_id) in all_refs {
|
||||||
// Record the current root ID
|
// Record the current root ID
|
||||||
let initial_root_id = graph.root.id.clone();
|
let initial_root_id = graph.root.id.clone();
|
||||||
|
|
||||||
// Update the reference with new content
|
// Update the reference with new content
|
||||||
let new_content = format!("updated_content_for_{}", ref_name).into_bytes();
|
let new_content = format!("updated_content_for_{}", ref_name).into_bytes();
|
||||||
graph.update_object_reference(&ref_name, new_content).unwrap();
|
graph
|
||||||
|
.update_object_reference(&ref_name, new_content)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Verify the reference itself changed
|
// Verify the reference itself changed
|
||||||
let updated_ref = graph.get_reference(&ref_name).unwrap();
|
let updated_ref = graph.get_reference(&ref_name).unwrap();
|
||||||
assert_ne!(updated_ref.id, original_ref_id,
|
assert_ne!(
|
||||||
"Reference {} should have changed ID after update", ref_name);
|
updated_ref.id, original_ref_id,
|
||||||
|
"Reference {} should have changed ID after update",
|
||||||
|
ref_name
|
||||||
|
);
|
||||||
|
|
||||||
// Verify the root ID changed
|
// Verify the root ID changed
|
||||||
assert_ne!(graph.root.id, initial_root_id,
|
assert_ne!(
|
||||||
"Root ID should change when reference {} is updated", ref_name);
|
graph.root.id, initial_root_id,
|
||||||
|
"Root ID should change when reference {} is updated",
|
||||||
|
ref_name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,69 +261,61 @@ fn test_root_id_changes_for_any_reference_update() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_reference_ids_stable_regardless_of_dependency_order() {
|
fn test_reference_ids_stable_regardless_of_dependency_order() {
|
||||||
// Create test dependencies
|
// Create test dependencies
|
||||||
let dep_a = Reference::new(
|
let dep_a = Reference::new(Some(String::from("content_a")), String::from("/dep_a")).to_arc();
|
||||||
Some(String::from("content_a")),
|
|
||||||
String::from("/dep_a"),
|
let dep_b = Reference::new(Some(String::from("content_b")), String::from("/dep_b")).to_arc();
|
||||||
).to_arc();
|
|
||||||
|
let dep_c = Reference::new(Some(String::from("content_c")), String::from("/dep_c")).to_arc();
|
||||||
let dep_b = Reference::new(
|
|
||||||
Some(String::from("content_b")),
|
let dep_d = Reference::new(Some(String::from("content_d")), String::from("/dep_d")).to_arc();
|
||||||
String::from("/dep_b"),
|
|
||||||
).to_arc();
|
|
||||||
|
|
||||||
let dep_c = Reference::new(
|
|
||||||
Some(String::from("content_c")),
|
|
||||||
String::from("/dep_c"),
|
|
||||||
).to_arc();
|
|
||||||
|
|
||||||
let dep_d = Reference::new(
|
|
||||||
Some(String::from("content_d")),
|
|
||||||
String::from("/dep_d"),
|
|
||||||
).to_arc();
|
|
||||||
|
|
||||||
// Create base reference
|
// Create base reference
|
||||||
let base_ref = Reference::new(
|
let base_ref = Reference::new(Some(String::from("base_content")), String::from("/base"));
|
||||||
Some(String::from("base_content")),
|
|
||||||
String::from("/base"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test multiple different orders of adding the same dependencies
|
// Test multiple different orders of adding the same dependencies
|
||||||
let orders = vec![
|
let orders = vec![
|
||||||
vec![dep_a.clone(), dep_b.clone(), dep_c.clone(), dep_d.clone()], // alphabetical
|
vec![dep_a.clone(), dep_b.clone(), dep_c.clone(), dep_d.clone()], // alphabetical
|
||||||
vec![dep_d.clone(), dep_c.clone(), dep_b.clone(), dep_a.clone()], // reverse alphabetical
|
vec![dep_d.clone(), dep_c.clone(), dep_b.clone(), dep_a.clone()], // reverse alphabetical
|
||||||
vec![dep_b.clone(), dep_d.clone(), dep_a.clone(), dep_c.clone()], // random order 1
|
vec![dep_b.clone(), dep_d.clone(), dep_a.clone(), dep_c.clone()], // random order 1
|
||||||
vec![dep_c.clone(), dep_a.clone(), dep_d.clone(), dep_b.clone()], // random order 2
|
vec![dep_c.clone(), dep_a.clone(), dep_d.clone(), dep_b.clone()], // random order 2
|
||||||
vec![dep_d.clone(), dep_a.clone(), dep_b.clone(), dep_c.clone()], // random order 3
|
vec![dep_d.clone(), dep_a.clone(), dep_b.clone(), dep_c.clone()], // random order 3
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut all_ids = Vec::new();
|
let mut all_ids = Vec::new();
|
||||||
|
|
||||||
for (i, order) in orders.iter().enumerate() {
|
for (i, order) in orders.iter().enumerate() {
|
||||||
let mut test_ref = base_ref.clone();
|
let mut test_ref = base_ref.clone();
|
||||||
|
|
||||||
// Add dependencies in this specific order
|
// Add dependencies in this specific order
|
||||||
for dep in order {
|
for dep in order {
|
||||||
test_ref = test_ref.add_dep(dep.clone());
|
test_ref = test_ref.add_dep(dep.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
all_ids.push(test_ref.id.clone());
|
all_ids.push(test_ref.id.clone());
|
||||||
|
|
||||||
// Verify that dependencies are always sorted lexicographically regardless of add order
|
// Verify that dependencies are always sorted lexicographically regardless of add order
|
||||||
for j in 0..test_ref.dependents.len() - 1 {
|
for j in 0..test_ref.dependents.len() - 1 {
|
||||||
let current = &test_ref.dependents[j];
|
let current = &test_ref.dependents[j];
|
||||||
let next = &test_ref.dependents[j + 1];
|
let next = &test_ref.dependents[j + 1];
|
||||||
|
|
||||||
assert!(current.name < next.name,
|
assert!(
|
||||||
"Dependencies should be lexicographically ordered. Order {}: Found '{}' before '{}'",
|
current.name < next.name,
|
||||||
i, current.name, next.name);
|
"Dependencies should be lexicographically ordered. Order {}: Found '{}' before '{}'",
|
||||||
|
i,
|
||||||
|
current.name,
|
||||||
|
next.name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify all IDs are identical
|
// Verify all IDs are identical
|
||||||
let first_id = &all_ids[0];
|
let first_id = &all_ids[0];
|
||||||
for (i, id) in all_ids.iter().enumerate() {
|
for (i, id) in all_ids.iter().enumerate() {
|
||||||
assert_eq!(id, first_id,
|
assert_eq!(
|
||||||
"Reference ID should be stable regardless of dependency add order. Order {} produced different ID", i);
|
id, first_id,
|
||||||
|
"Reference ID should be stable regardless of dependency add order. Order {} produced different ID",
|
||||||
|
i
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,7 +323,7 @@ fn test_reference_ids_stable_regardless_of_dependency_order() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_dependencies_lexicographically_ordered() {
|
fn test_dependencies_lexicographically_ordered() {
|
||||||
let graph = create_test_graph();
|
let graph = create_test_graph();
|
||||||
|
|
||||||
// Check all references to ensure their dependents are lexicographically ordered
|
// Check all references to ensure their dependents are lexicographically ordered
|
||||||
for (_, reference) in graph.refs.as_ref() {
|
for (_, reference) in graph.refs.as_ref() {
|
||||||
if reference.dependents.len() > 1 {
|
if reference.dependents.len() > 1 {
|
||||||
@ -322,44 +331,46 @@ fn test_dependencies_lexicographically_ordered() {
|
|||||||
for i in 0..reference.dependents.len() - 1 {
|
for i in 0..reference.dependents.len() - 1 {
|
||||||
let current = &reference.dependents[i];
|
let current = &reference.dependents[i];
|
||||||
let next = &reference.dependents[i + 1];
|
let next = &reference.dependents[i + 1];
|
||||||
|
|
||||||
assert!(current.name < next.name,
|
assert!(
|
||||||
"Dependencies should be lexicographically ordered by name. Found '{}' before '{}'",
|
current.name < next.name,
|
||||||
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
|
// Also verify that when we add dependencies, they maintain the correct order
|
||||||
let mut test_ref = Reference::new(
|
let mut test_ref = Reference::new(
|
||||||
Some(String::from("test_content")),
|
Some(String::from("test_content")),
|
||||||
String::from("/test_ordering"),
|
String::from("/test_ordering"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add dependencies in non-lexicographical order
|
// Add dependencies in non-lexicographical order
|
||||||
let dep_c = Reference::new(
|
let dep_c = Reference::new(Some(String::from("c_content")), String::from("/c")).to_arc();
|
||||||
Some(String::from("c_content")),
|
|
||||||
String::from("/c"),
|
let dep_a = Reference::new(Some(String::from("a_content")), String::from("/a")).to_arc();
|
||||||
).to_arc();
|
|
||||||
|
let dep_b = Reference::new(Some(String::from("b_content")), String::from("/b")).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
|
// Add in non-lexicographical order
|
||||||
test_ref = test_ref.add_dep(dep_c.clone());
|
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_a.clone());
|
||||||
test_ref = test_ref.add_dep(dep_b.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'");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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