laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Filesystem test harness used by `filesystem_test!` invocations in
//! `tests/test_cases/filesystem_tests.rs`. Those invocations are all commented
//! out, so the harness compiles dead — kept under #[allow(dead_code)] until
//! the test cases are re-enabled (see `laburnum-9g2`).
#![allow(dead_code)]

use {
  crate::{
    Uri,
    fs::FS,
  },
  slotmap::{
    SlotMap,
    new_key_type,
  },
  std::collections::BTreeMap,
  tempfile::TempDir,
};

new_key_type! {
    pub struct NodeKey;
}

#[derive(Debug)]
pub struct TestNode {
  pub name:     String,
  pub content:  Option<Vec<u8>>,
  pub children: BTreeMap<String, NodeKey>,
}

pub struct TestData {
  nodes: SlotMap<NodeKey, TestNode>,
  root:  NodeKey,
  globs: BTreeMap<String, Vec<String>>,
}

impl TestData {
  pub fn new() -> Self {
    let mut nodes: SlotMap<NodeKey, TestNode> = SlotMap::with_key();
    let root = nodes.insert(TestNode {
      name:     String::from(""),
      content:  None,
      children: BTreeMap::new(),
    });
    TestData {
      nodes,
      root,
      globs: BTreeMap::new(),
    }
  }

  pub fn add_glob_test(
    &mut self,
    pattern: &str,
    expected: Vec<String>,
  ) -> &mut Self {
    self.globs.insert(pattern.to_string(), expected);
    self
  }

  pub fn dir<F>(&mut self, name: &str, f: F) -> &mut Self
  where
    F: FnOnce(&mut DirBuilder),
  {
    let node_key = self.nodes.insert(TestNode {
      name:     name.to_string(),
      content:  None,
      children: BTreeMap::new(),
    });

    if let Some(root) = self.nodes.get_mut(self.root) {
      root.children.insert(name.to_string(), node_key);
    }

    let mut builder = DirBuilder {
      nodes:   &mut self.nodes,
      current: node_key,
    };
    f(&mut builder);
    self
  }

  pub fn create_physical(&self) -> (TempDir, Uri) {
    let temp_dir = tempfile::Builder::new()
      .prefix("laburnum-fs-test")
      .tempdir()
      .expect("Failed to create temp directory");

    let base_uri =
      Uri::from_file_path(temp_dir.path()).expect("Failed to create base Uri");

    self.create_fs_structure(temp_dir.path(), self.root);

    (temp_dir, base_uri)
  }

  // Helper method to get all paths and their contents
  pub fn flatten_structure(&self) -> Vec<(String, Option<Vec<u8>>)> {
    let mut result = Vec::new();
    self.flatten_node("", self.root, &mut result);
    result
  }

  fn flatten_node(
    &self,
    prefix: &str,
    node_key: NodeKey,
    result: &mut Vec<(String, Option<Vec<u8>>)>,
  ) {
    if let Some(node) = self.nodes.get(node_key) {
      let current_path = if prefix.is_empty() {
        node.name.clone()
      } else {
        format!("{}/{}", prefix, node.name)
      };

      if !current_path.is_empty() {
        result.push((current_path.clone(), node.content.clone()));
      }

      for child_key in node.children.values() {
        self.flatten_node(&current_path, *child_key, result);
      }
    }
  }

  fn create_fs_structure(
    &self,
    base_path: &std::path::Path,
    node_key: NodeKey,
  ) {
    if let Some(node) = self.nodes.get(node_key) {
      let current_path = base_path.join(&node.name);

      if let Some(content) = &node.content {
        std::fs::write(&current_path, content).expect("Failed to write file");
      } else if !node.name.is_empty() {
        std::fs::create_dir_all(&current_path)
          .expect("Failed to create directory");
      }

      for child_key in node.children.values() {
        self.create_fs_structure(&current_path, *child_key);
      }
    }
  }
}

pub struct DirBuilder<'a> {
  nodes:   &'a mut SlotMap<NodeKey, TestNode>,
  current: NodeKey,
}

impl<'a> DirBuilder<'a> {
  pub fn file(&mut self, name: &str, content: &str) {
    // Remove return type
    let node_key = self.nodes.insert(TestNode {
      name:     name.to_string(),
      content:  Some(content.as_bytes().to_vec()),
      children: BTreeMap::new(),
    });

    if let Some(current) = self.nodes.get_mut(self.current) {
      current.children.insert(name.to_string(), node_key);
    }
  }

  pub fn dir<F>(&mut self, name: &str, f: F)
  // Remove return type
  where
    F: FnOnce(&mut DirBuilder),
  {
    let node_key = self.nodes.insert(TestNode {
      name:     name.to_string(),
      content:  None,
      children: BTreeMap::new(),
    });

    if let Some(current) = self.nodes.get_mut(self.current) {
      current.children.insert(name.to_string(), node_key);
    }

    let mut builder = DirBuilder {
      nodes:   self.nodes,
      current: node_key,
    };
    f(&mut builder);
  }
}

pub fn run_filesystem_test(fs: FS, test_data: TestData, base_uri: Uri) {
  let structure = test_data.flatten_structure();

  println!("\nTesting filesystem with base_uri: {base_uri}");
  println!("Test structure contains {} entries:", structure.len());
  for (path, content) in &structure {
    println!(
      "  {} ({})",
      path,
      if content.is_some() {
        "file"
      } else {
        "directory"
      }
    );
  }

  // Phase 0: Create Initial Structure
  println!("\nPhase 0: Creating initial structure");
  for (path, content) in &structure {
    let uri = base_uri.join(path).unwrap_or_else(|| {
      panic!("Failed to create Uri for path '{path}'");
    });

    if content.is_none() {
      match fs.dir(&uri) {
        | Ok(_) => println!("✓ Created directory: {path}"),
        | Err(e) => panic!("Failed to create directory '{path}': {e}"),
      }
    } else {
      match fs.write(&uri, content.as_ref().unwrap()) {
        | Ok(_) => println!("✓ Created file: {path}"),
        | Err(e) => panic!("Failed to create file '{path}': {e}"),
      }
    }
  }

  // Phase 1: Initial Structure Verification
  println!("\nPhase 1: Verifying initial structure");
  for (path, content) in &structure {
    let uri = base_uri.join(path).unwrap_or_else(|| {
      panic!("Failed to create Uri for path '{path}'");
    });

    match fs.metadata(&uri) {
      | Ok(metadata) => {
        println!("✓ Found entry: {path}");
        assert_eq!(
          metadata.is_dir,
          content.is_none(),
          "Type mismatch for '{}': expected {}, got directory",
          path,
          if content.is_none() {
            "directory"
          } else {
            "file"
          }
        );

        if let Some(content) = content {
          assert_eq!(
            metadata.len,
            content.len() as u64,
            "Size mismatch for file '{}': expected {} bytes, got {} bytes",
            path,
            content.len(),
            metadata.len
          );
        }
      },
      | Err(e) => {
        panic!("Failed to get metadata for '{path}' (uri: {uri}): {e}");
      },
    }
  }

  // Phase 2: Content Verification
  println!("\nPhase 2: Verifying file contents");
  for (path, content) in &structure {
    if let Some(expected_content) = content {
      let uri = base_uri.join(path).unwrap();
      match fs.read(&uri) {
        | Ok(actual_content) => {
          if actual_content == *expected_content {
            println!("✓ Content verified: {path}");
          } else {
            panic!(
              "Content mismatch for '{}':\nExpected: {}\nActual: {}\n",
              path,
              String::from_utf8_lossy(expected_content),
              String::from_utf8_lossy(&actual_content)
            );
          }
        },
        | Err(e) => {
          panic!("Failed to read content from '{path}': {e}");
        },
      }
    }
  }

  // Phase 3: Pattern Matching Testing
  println!("\nPhase 3: Testing pattern matching");
  for (pattern, expected_paths) in test_data.globs {
    println!("\nTesting pattern: {pattern}");

    match fs.find(&base_uri, std::slice::from_ref(&pattern)) {
      | Ok(entries) => {
        let found_paths: Vec<String> = entries
          .into_iter()
          .map(|e| {
            let full_path = e.path();
            if full_path.scheme().as_str() == "memory" {
              full_path.path_str().trim_start_matches('/').to_string()
            } else {
              let base_path = base_uri.path_str().trim_end_matches('/');
              full_path
                .path_str()
                .trim_start_matches(base_path)
                .trim_start_matches('/')
                .to_string()
            }
          })
          .collect();

        println!("Found {} matches:", found_paths.len());
        for path in &found_paths {
          println!("  {path}");
        }

        // Verify all expected paths are found
        for expected in &expected_paths {
          assert!(
            found_paths.contains(expected),
            "Expected path '{}' not found for pattern '{}'\nFound paths:\n{}\n",
            expected,
            pattern,
            found_paths.join("\n  ")
          );
        }

        // Verify no unexpected paths are found
        for found in &found_paths {
          assert!(
            expected_paths.contains(found),
            "Unexpected path '{}' found for pattern '{}'\nExpected paths:\n{}\n",
            found,
            pattern,
            expected_paths.join("\n  ")
          );
        }
      },
      | Err(e) => {
        panic!("Failed to perform pattern matching for '{pattern}': {e}");
      },
    }
  }

  // Phase 4: Deletion Testing
  println!("\nPhase 4: Testing deletion");
  // Collect and sort paths to ensure we delete deepest paths first
  let mut paths: Vec<_> = structure.iter().collect();
  paths.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len())); // Sort by path length, longest (deepest) first

  for (path, _) in paths {
    let uri = base_uri.join(path).unwrap();

    // Verify existence before deletion
    match fs.metadata(&uri) {
      | Ok(_) => println!("✓ Verified existence: {path}"),
      | Err(e) => {
        panic!("Entry '{path}' doesn't exist before deletion attempt: {e}")
      },
    }

    // Attempt deletion
    if let Err(e) = fs.delete(&uri) {
      panic!("Failed to delete '{path}': {e}");
    }
    println!("✓ Deleted: {path}");

    // Verify deletion
    match fs.metadata(&uri) {
      | Ok(_) => panic!("Entry '{path}' still exists after deletion"),
      | Err(_) => println!("✓ Verified deletion: {path}"),
    }
  }

  println!("\nAll tests passed successfully!");
}

#[cfg(test)]
mod test_cases;