@anysphere/file-service 0.0.0-e1f2f04d → 0.0.0-e36a46ab

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,13 @@
1
1
  use super::file_utils;
2
2
  use sha2::Digest;
3
+ use std::collections::{BTreeMap, HashSet};
3
4
  use std::path::PathBuf;
4
- use std::{collections::HashMap, fs, path::Path, sync::Arc};
5
+ use std::vec;
6
+ use std::{fs, path::Path, sync::Arc};
5
7
  use tokio::sync::RwLock;
6
8
  use tonic::async_trait;
9
+ use tracing::info;
10
+
7
11
  pub mod local_construction;
8
12
  pub mod test;
9
13
 
@@ -12,8 +16,10 @@ pub type MerkleNodePtr = Arc<RwLock<MerkleNode>>;
12
16
  pub struct MerkleTree {
13
17
  root_path: String,
14
18
  root: MerkleNodePtr,
15
- files: HashMap<String, File>,
16
- cursor: Option<MerkleNodePtr>,
19
+ files: BTreeMap<String, File>,
20
+ cursor: Option<usize>,
21
+ git_ignored_files_and_dirs: HashSet<String>,
22
+ is_git_repo: bool,
17
23
  }
18
24
 
19
25
  #[derive(Debug)]
@@ -57,6 +63,8 @@ pub trait LocalConstruction {
57
63
 
58
64
  async fn construct_merkle_tree(
59
65
  root_directory: String,
66
+ git_ignored_files_and_dirs: HashSet<String>,
67
+ is_git_repo: bool
60
68
  ) -> Result<MerkleTree, anyhow::Error>;
61
69
 
62
70
  async fn update_file(
@@ -87,27 +95,38 @@ impl MerkleTree {
87
95
  pub fn empty_tree() -> MerkleTree {
88
96
  MerkleTree {
89
97
  root: Arc::new(RwLock::new(MerkleNode::empty_node(None, None))),
90
- files: HashMap::new(),
98
+ files: BTreeMap::new(),
91
99
  root_path: "".to_string(),
92
100
  cursor: None,
101
+ git_ignored_files_and_dirs: HashSet::new(),
102
+ is_git_repo: false
93
103
  }
94
104
  }
95
105
 
96
106
  pub async fn get_subtree_hash(
97
107
  &self,
98
- path: String,
108
+ absolute_path: &str,
99
109
  ) -> Result<String, anyhow::Error> {
100
- let path = PathBuf::from(path);
101
- let node = match self.files.get(path.to_str().unwrap()) {
110
+ let node = match self.files.get(absolute_path) {
102
111
  Some(file) => file.node.clone(),
103
112
  None => {
104
- return Err(anyhow::anyhow!("Could not find file in tree!"));
113
+ let all_files: Vec<String> = self.files.keys().cloned().collect();
114
+ return Err(anyhow::anyhow!(
115
+ "Could not find file in tree! Looking for: {}. All files: {:?}",
116
+ absolute_path,
117
+ all_files
118
+ ));
105
119
  }
106
120
  };
107
121
 
108
122
  let node_reader = node.read().await;
109
123
  let node_hash = node_reader.hash.clone();
110
124
 
125
+ info!(
126
+ "get_subtree_hash for path: {}, node_hash: {}",
127
+ absolute_path, node_hash
128
+ );
129
+
111
130
  Ok(node_hash)
112
131
  }
113
132
 
@@ -132,6 +151,43 @@ impl MerkleTree {
132
151
  Ok(count)
133
152
  }
134
153
 
154
+ pub async fn get_num_embeddable_files_in_subtree(
155
+ &self,
156
+ absolute_path: PathBuf,
157
+ ) -> Result<i32, anyhow::Error> {
158
+ let mut count = 0;
159
+
160
+ let absolute_path = match absolute_path.to_str() {
161
+ Some(s) => s.to_string(),
162
+ None => {
163
+ return Err(anyhow::anyhow!(
164
+ "get_num_embeddable_files_in_subtree: Failed to convert path to string"
165
+ ))
166
+ }
167
+ };
168
+
169
+ // TODO(sualeh): worth keeping this list sorted. its now a btree
170
+
171
+ for (_, file) in &self.files {
172
+ let file_reader = file.node.read().await;
173
+ match &file_reader.node_type {
174
+ NodeType::File(file_name) => {
175
+ if file_name.contains(&absolute_path) {
176
+ count += 1;
177
+ }
178
+ }
179
+ NodeType::Branch(_) => {
180
+ continue;
181
+ }
182
+ NodeType::ErrorNode(_) => {
183
+ continue;
184
+ }
185
+ }
186
+ }
187
+
188
+ Ok(count)
189
+ }
190
+
135
191
  pub async fn get_all_files(&self) -> Result<Vec<String>, anyhow::Error> {
136
192
  let mut files = Vec::new();
137
193
 
@@ -188,83 +244,125 @@ impl MerkleTree {
188
244
  pub async fn get_next_file_to_embed(
189
245
  &mut self,
190
246
  ) -> Result<(String, Vec<String>), anyhow::Error> {
191
- // the plan is to do an in-order traversal of the tree.
192
-
193
- // first the edge case to deal with:
194
- // cursor == None
195
- if self.cursor.is_none() {
196
- // If the root is a file, return its name.
197
- if let NodeType::File(file_path) = &self.root.read().await.node_type {
198
- return Ok((file_path.clone(), vec![]));
199
- }
200
-
201
- // if the path is not empty, we can iterate till we find the first child.
202
- let mut potential_first_child = self.root.clone();
203
- let mut is_branch = true;
204
- let mut path = Vec::new();
205
-
206
- while is_branch {
207
- let node = {
208
- let potential_first_child_reader = potential_first_child.read().await;
209
- match &potential_first_child_reader.node_type {
210
- NodeType::Branch(branch) => branch.clone(),
211
- NodeType::File(_) => {
212
- return Err(anyhow::anyhow!(
213
- "get_next_file_to_embed: This should not happen! the branch happened to be file."
214
- ));
215
- }
216
- NodeType::ErrorNode(_) => {
217
- return Err(anyhow::anyhow!("Cursor is an error node!"));
218
- }
219
- }
220
- };
247
+ // if the cursor is none, set it to 0
248
+ let cursor = match self.cursor {
249
+ Some(cursor) => cursor,
250
+ None => {
251
+ self.cursor = Some(0);
252
+ 0
253
+ }
254
+ };
255
+
256
+ // get the thing at the cursor. while we dont find a file, we keep incrementing the cursor.
257
+ let mut cursor = cursor;
258
+ loop {
259
+ // O(log n)
260
+ let file = match self.files.values().nth(cursor) {
261
+ Some(file) => file,
262
+ None => {
263
+ return Err(anyhow::anyhow!("Could not find file to embed!"));
264
+ }
265
+ };
221
266
 
222
- let current_node_name = &node.0;
223
- let child_list = &node.1;
267
+ let file_reader = file.node.read().await;
268
+ match &file_reader.node_type {
269
+ NodeType::File(f) => {
270
+ // update the cursor.
271
+ self.cursor = Some(cursor + 1);
272
+ let spline = self.get_spline(f).await?;
273
+ return Ok((f.clone(), spline));
274
+ }
275
+ NodeType::Branch(_) => {
276
+ cursor += 1;
277
+ continue;
278
+ }
279
+ NodeType::ErrorNode(_) => {
280
+ cursor += 1;
281
+ continue;
282
+ }
283
+ }
284
+ }
285
+ }
224
286
 
225
- if let Some(c) = child_list.first() {
226
- let c_reader = c.read().await;
287
+ pub async fn get_all_dir_files_to_embed(
288
+ &self,
289
+ absolute_path: &str,
290
+ ) -> Result<Vec<String>, anyhow::Error> {
291
+ let mut files = Vec::new();
227
292
 
228
- match &c_reader.node_type {
229
- NodeType::File(file_path) => {
230
- // must set the cursor!
231
- self.cursor = Some(c.clone());
293
+ // 1. should check that this absolute path is actually a directory.
294
+ let file_node = self.files.get(absolute_path);
295
+ if file_node.is_none() {
296
+ return Err(anyhow::anyhow!("Could not find directory the in tree!"));
297
+ }
232
298
 
233
- return Ok((file_path.clone(), path));
234
- }
235
- NodeType::Branch(_) => {
236
- potential_first_child = c.clone();
237
- is_branch = true;
299
+ for (file_path, f) in &self.files {
300
+ if !file_path.contains(absolute_path) {
301
+ continue;
302
+ }
238
303
 
239
- // add the path to the current node.
240
- path.push(current_node_name.clone());
241
- }
242
- NodeType::ErrorNode(_) => {
243
- return Err(anyhow::anyhow!("Cursor is an error node!"));
244
- }
245
- }
246
- } else {
247
- // If the root has no children, return an error.
248
- return Err(anyhow::anyhow!("Root has no children!"));
304
+ match f.node.read().await.node_type {
305
+ NodeType::File(_) => {
306
+ files.push(file_path.clone());
307
+ }
308
+ NodeType::Branch(_) => {
309
+ continue;
310
+ }
311
+ NodeType::ErrorNode(_) => {
312
+ continue;
249
313
  }
250
314
  }
251
315
  }
252
316
 
253
- // THE DEFAULT CASE:
254
- // we already have a cursor at a file.
255
-
256
- // UNWRAP checked and fine. see the none case above.
257
- let cursor_name = self.cursor.as_ref().unwrap();
258
- let cursor_reader = cursor_name.read().await;
317
+ Ok(files)
318
+ }
259
319
 
260
- // invariant: you must be a file!!
320
+ // TODO(sualeh): i need tests for this!!
321
+ pub async fn get_spline(
322
+ &self,
323
+ absolute_path: &str,
324
+ ) -> Result<Vec<String>, anyhow::Error> {
325
+ info!("get_spline called with absolute_path: {}", absolute_path);
326
+ let mut files = Vec::new();
261
327
 
262
- // everytime we get to a child list, we will add all the children to a fifo, and then pull from it as long as we need it.
328
+ let current_node = match self.files.get(absolute_path) {
329
+ Some(node) => {
330
+ info!("Found node for absolute_path: {}", absolute_path);
331
+ node.node.clone()
332
+ }
333
+ None => {
334
+ info!("File not found for absolute_path: {}", absolute_path);
335
+ return Err(anyhow::anyhow!("File not found: {}", absolute_path));
336
+ }
337
+ };
263
338
 
264
- // algorithm:
265
- // 1.
339
+ let mut stack = Vec::new();
340
+ stack.push(current_node);
341
+
342
+ while let Some(node) = stack.pop() {
343
+ let parent = node.read().await.parent.clone();
344
+ if let Some(parent) = parent {
345
+ info!("Adding parent hash to files vector");
346
+ {
347
+ let parent_node = parent.read().await;
348
+ match &parent_node.node_type {
349
+ NodeType::File(file_name) => {
350
+ files.push(file_name.clone());
351
+ }
352
+ NodeType::Branch((branch_name, _)) => {
353
+ files.push(branch_name.clone());
354
+ }
355
+ _ => {
356
+ continue;
357
+ }
358
+ }
359
+ }
266
360
 
267
- Err(anyhow::anyhow!("Could not find file to embed!"))
361
+ stack.push(parent);
362
+ }
363
+ }
364
+ info!("Returning files vector with {} elements", files.len());
365
+ Ok(files)
268
366
  }
269
367
 
270
368
  /// creates a new node and attaches it to the current tree.
@@ -302,12 +400,19 @@ impl MerkleTree {
302
400
  // 1. the path is empty. this means that the ancestor is the root.
303
401
  // 2. the path is non-empty. that means there exist a non-empty element btwn till the root.
304
402
 
403
+ let absolute_root_path = self.root_path.clone();
305
404
  let new_node = match path.len() {
306
405
  0 => {
307
406
  // this means that the ancestor is the root.
308
407
  // we need to create a new node and attach it to the ancestor.
309
- let new_node =
310
- MerkleNode::new(file_path.clone(), Some(ancestor.clone())).await;
408
+ let new_node = MerkleNode::new(
409
+ file_path.clone(),
410
+ Some(ancestor.clone()),
411
+ &self.git_ignored_files_and_dirs,
412
+ &absolute_root_path.as_str(),
413
+ self.is_git_repo
414
+ )
415
+ .await;
311
416
  ancestor.write().await.attach_child(new_node.clone()).await;
312
417
  new_node
313
418
  }
@@ -318,9 +423,14 @@ impl MerkleTree {
318
423
  // UNSURE: not sure this is the correct thing to do but it is the fastest.
319
424
  // get the last thing that is not in the tree.
320
425
  let first_child_path = path.last().unwrap();
321
- let first_child =
322
- MerkleNode::new(first_child_path.clone(), Some(ancestor.clone()))
323
- .await;
426
+ let first_child = MerkleNode::new(
427
+ first_child_path.clone(),
428
+ Some(ancestor.clone()),
429
+ &self.git_ignored_files_and_dirs,
430
+ &absolute_root_path.as_str(),
431
+ self.is_git_repo
432
+ )
433
+ .await;
324
434
 
325
435
  // TODO(sualeh): we should do an assertion check that the entire vec is contained here.
326
436
 
@@ -597,18 +707,58 @@ use std::future::Future;
597
707
  use std::pin::Pin;
598
708
 
599
709
  type PinnedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
710
+ type IgnoredFiles = HashSet<String>;
600
711
 
601
712
  impl MerkleNode {
602
713
  /// please be careful using this.
603
714
  async fn __new_unchecked(
604
715
  file_or_directory: String,
605
716
  parent: ParentPtr,
717
+ ignored_files: &IgnoredFiles,
718
+ absolute_root_path: &str,
719
+ is_git_repo: bool,
606
720
  ) -> MerkleNodePtr {
607
- MerkleNode::construct_node(Path::new(&file_or_directory), parent).await
721
+ // // check if the root is a git directory.
722
+ // let is_git_repo =
723
+ // match git_utils::is_git_directory(absolute_root_path).await {
724
+ // Ok(is_git_repo) => is_git_repo,
725
+ // Err(_e) => false,
726
+ // };
727
+ let bypass_git = !is_git_repo;
728
+
729
+ MerkleNode::construct_node(
730
+ Path::new(&file_or_directory),
731
+ parent,
732
+ ignored_files,
733
+ absolute_root_path,
734
+ bypass_git,
735
+ )
736
+ .await
608
737
  }
609
738
 
610
- async fn new(file_or_directory: PathBuf, parent: ParentPtr) -> MerkleNodePtr {
611
- MerkleNode::construct_node(Path::new(&file_or_directory), parent).await
739
+ async fn new(
740
+ absolute_file_or_directory: PathBuf,
741
+ parent: ParentPtr,
742
+ ignored_files: &IgnoredFiles,
743
+ absolute_root_path: &str,
744
+ is_git_repo: bool,
745
+ ) -> MerkleNodePtr {
746
+ let bypass_git = !is_git_repo;
747
+
748
+ info!(
749
+ "constructing node for absolute_file_or_directory: {:?}",
750
+ absolute_file_or_directory
751
+ );
752
+ info!("bypass_git: {}, is_git_repo: {}", bypass_git, is_git_repo);
753
+
754
+ MerkleNode::construct_node(
755
+ Path::new(&absolute_file_or_directory),
756
+ parent,
757
+ ignored_files,
758
+ absolute_root_path,
759
+ bypass_git,
760
+ )
761
+ .await
612
762
  }
613
763
 
614
764
  /// NOT added to the tree by default.
@@ -619,38 +769,51 @@ impl MerkleNode {
619
769
  // let file_hash = self.files.get_mut(&file_path).unwrap();
620
770
 
621
771
  fn construct_node<'a>(
622
- file_or_directory: &'a Path,
772
+ absolute_file_or_directory: &'a Path,
623
773
  parent: ParentPtr,
774
+ ignored_files: &'a IgnoredFiles,
775
+ absolute_root_path: &'a str,
776
+ bypass_git: bool,
624
777
  ) -> PinnedFuture<'a, MerkleNodePtr> {
625
778
  Box::pin(async move {
626
779
  // check if it is a file
627
- let path_str = file_or_directory.to_str().unwrap().to_string();
628
- if file_or_directory.is_file() {
780
+ let path_str = absolute_file_or_directory.to_str().unwrap().to_string();
781
+ if absolute_file_or_directory.is_file() {
629
782
  return Arc::new(RwLock::new(
630
783
  MerkleNode::construct_file_node_or_error_node(
631
- file_or_directory,
784
+ absolute_file_or_directory,
632
785
  parent,
786
+ ignored_files,
633
787
  )
634
788
  .await,
635
789
  ));
636
790
  }
637
791
 
638
792
  // check if the directory fails the bad dir test.
639
- let is_bad_dir = file_utils::is_in_bad_dir(file_or_directory);
793
+ let is_bad_dir = file_utils::is_in_bad_dir(absolute_file_or_directory);
640
794
  if is_bad_dir.is_err() || is_bad_dir.unwrap_or(false) {
641
795
  // println!("skipping directory: {}", path_str);
642
796
  return Arc::new(RwLock::new(MerkleNode::empty_node(
643
- Some(file_or_directory),
797
+ Some(absolute_file_or_directory),
644
798
  Some("Directory is in bad dir!".to_string()),
645
799
  )));
646
800
  }
647
801
 
648
- let entries = fs::read_dir(file_or_directory);
802
+ let is_git_ignored_dir = ignored_files.contains(&path_str);
803
+
804
+ if is_git_ignored_dir && !bypass_git {
805
+ return Arc::new(RwLock::new(MerkleNode::empty_node(
806
+ Some(absolute_file_or_directory),
807
+ Some("Directory is git ignored!".to_string()),
808
+ )));
809
+ }
810
+
811
+ let entries = fs::read_dir(absolute_file_or_directory);
649
812
  match entries {
650
813
  Ok(_) => (),
651
814
  Err(e) => {
652
815
  return Arc::new(RwLock::new(MerkleNode::empty_node(
653
- Some(file_or_directory),
816
+ Some(absolute_file_or_directory),
654
817
  Some(e.to_string()),
655
818
  )));
656
819
  }
@@ -670,13 +833,19 @@ impl MerkleNode {
670
833
  match entry {
671
834
  Ok(entry) => {
672
835
  children.push(
673
- MerkleNode::construct_node(&entry.path(), Some(node.clone()))
674
- .await,
836
+ MerkleNode::construct_node(
837
+ &entry.path(),
838
+ Some(node.clone()),
839
+ ignored_files,
840
+ absolute_root_path,
841
+ bypass_git,
842
+ )
843
+ .await,
675
844
  );
676
845
  }
677
846
  Err(e) => {
678
847
  children.push(Arc::new(RwLock::new(MerkleNode::empty_node(
679
- Some(file_or_directory),
848
+ Some(absolute_file_or_directory),
680
849
  Some(e.to_string()),
681
850
  ))));
682
851
  }
@@ -696,31 +865,37 @@ impl MerkleNode {
696
865
  }
697
866
 
698
867
  async fn construct_file_node(
699
- file_path: &Path,
868
+ absolute_file_path: &Path,
700
869
  parent: ParentPtr,
870
+ ignored_files: &IgnoredFiles,
701
871
  ) -> Result<MerkleNode, String> {
702
- let file_str = file_path
872
+ let file_str = absolute_file_path
703
873
  .to_str()
704
874
  .ok_or("Could not convert file path to string!")?
705
875
  .to_string();
706
876
  // first see if it passes the
707
- match file_utils::is_good_file(file_path) {
877
+ match file_utils::is_good_file(absolute_file_path) {
708
878
  Ok(_) => {}
709
879
  Err(e) => {
710
880
  return Err(format!("File failed runtime checks! {}", e.to_string()));
711
881
  }
712
882
  }
713
883
 
714
- // read the file_content to a buffer
715
- let file_content = match tokio::fs::read(file_path).await {
716
- Ok(content) => content,
717
- Err(e) => {
718
- return Err(format!("Could not read file! {}", e.to_string()));
884
+ // check if the file is in the git ignore buffer.
885
+ // this is a bug right because we are not checking absoluteness here.
886
+ match ignored_files.contains(&file_str) {
887
+ true => {
888
+ return Err(format!("File is in git ignore buffer!"));
719
889
  }
720
- };
890
+ false => {}
891
+ }
721
892
 
722
893
  // check if the file passes runtime checks.
723
- match file_utils::is_good_file_runtime_check(file_path, &file_content).await
894
+ match file_utils::is_good_file_runtime_check(
895
+ absolute_file_path,
896
+ // &file_content,
897
+ )
898
+ .await
724
899
  {
725
900
  Ok(_) => {}
726
901
  Err(e) => {
@@ -728,15 +903,14 @@ impl MerkleNode {
728
903
  }
729
904
  }
730
905
 
731
- let file_content = match std::str::from_utf8(&file_content) {
732
- Ok(content) => content,
733
- Err(e) => {
734
- return Err(format!(
735
- "UTF8 Failure. Could not convert file content to string! {}",
736
- e.to_string()
737
- ));
738
- }
739
- };
906
+ // read the file_content to a buffer
907
+ let file_content =
908
+ match file_utils::read_string_without_bom(absolute_file_path).await {
909
+ Ok(content) => content,
910
+ Err(e) => {
911
+ return Err(format!("Could not read file! {}", e.to_string()));
912
+ }
913
+ };
740
914
 
741
915
  let file_hash = compute_hash(&file_content);
742
916
  let node = MerkleNode {
@@ -751,15 +925,22 @@ impl MerkleNode {
751
925
  }
752
926
 
753
927
  async fn construct_file_node_or_error_node(
754
- file_path: &Path,
928
+ absolute_file_path: &Path,
755
929
  parent: ParentPtr,
930
+ ignored_files: &IgnoredFiles,
756
931
  ) -> MerkleNode {
757
- let node = match MerkleNode::construct_file_node(file_path, parent).await {
932
+ let node = match MerkleNode::construct_file_node(
933
+ absolute_file_path,
934
+ parent,
935
+ ignored_files,
936
+ )
937
+ .await
938
+ {
758
939
  Ok(node) => node,
759
940
  Err(e) => {
760
941
  // println!("constructing error node. error: {}", e);
761
942
  // println!("file_path: {:?}", file_path);
762
- MerkleNode::empty_node(Some(file_path), Some(e))
943
+ MerkleNode::empty_node(Some(absolute_file_path), Some(e))
763
944
  }
764
945
  };
765
946
 
@@ -785,15 +966,51 @@ impl MerkleNode {
785
966
 
786
967
  async fn compute_branch_hash(children: &[MerkleNodePtr]) -> String {
787
968
  let mut hasher = sha2::Sha256::new();
969
+ let mut names_and_hashes = vec![];
970
+ let mut non_zero_children = 0;
971
+
788
972
  for child in children {
789
973
  // check if it is an error node
790
974
  let child_reader = child.read().await;
791
- if let NodeType::ErrorNode(_) = &child_reader.node_type {
975
+
976
+ match &child_reader.node_type {
977
+ NodeType::File(file_name) => {
978
+ non_zero_children += 1;
979
+ names_and_hashes.push((file_name.clone(), child_reader.hash.clone()));
980
+ }
981
+ NodeType::Branch((file_name, _)) => {
982
+ let hash = child_reader.hash.clone();
983
+ if hash == "" {
984
+ continue;
985
+ }
986
+
987
+ non_zero_children += 1;
988
+ names_and_hashes.push((file_name.clone(), hash));
989
+ }
990
+ NodeType::ErrorNode(_) => {
991
+ continue;
992
+ }
993
+ }
994
+ }
995
+
996
+ // sort the list of names and hashes by the hashes!!
997
+ names_and_hashes
998
+ .sort_by(|a, b| a.1.to_lowercase().cmp(&b.1.to_lowercase()));
999
+
1000
+ for (name, hash) in names_and_hashes {
1001
+ if hash == "" {
792
1002
  continue;
793
1003
  }
1004
+ info!("name: {}, hash: {}", name, hash);
1005
+ hasher.update(hash);
1006
+ }
794
1007
 
795
- hasher.update(child_reader.hash.as_bytes());
1008
+ if non_zero_children == 0 {
1009
+ // this means that the branch is empty.
1010
+ // we should return an empty string.
1011
+ return "".to_string();
796
1012
  }
1013
+
797
1014
  let result = hasher.finalize();
798
1015
  format!("{:x}", result)
799
1016
  }
@@ -43,8 +43,9 @@ mod tests {
43
43
  // let path = Path::new(&temp_dir_path);
44
44
 
45
45
  // Test construct_merkle_tree() function
46
+ let new_set = std::collections::HashSet::<String>::new();
46
47
  let tree =
47
- MerkleTree::construct_merkle_tree(temp_dir_path.clone()).await;
48
+ MerkleTree::construct_merkle_tree(temp_dir_path.clone(), new_set, false).await;
48
49
  let mut tree = match tree {
49
50
  Ok(tree) => {
50
51
  assert_eq!(tree.files.len(), 2);
package/src/test.rs ADDED
@@ -0,0 +1,5 @@
1
+ #[cfg(test)]
2
+ mod tests {
3
+ use super::super::*;
4
+ use std::path::PathBuf;
5
+ }