Viewing: layout_test.rs
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
use lustreapi_sys::{LLAPI_LAYOUT_DEFAULT, *};
use rand::prelude::*;
use rustreapi::{
CompEntryFlags, CompUse, Fid, Layout, LayoutGetFlags, LustrePath, OpenOptions, ost_count,
};
use std::{
env, fs,
os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt},
path::PathBuf,
process::Command,
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
static T0_SIZE: u64 = 1024 * 1024;
static T0_COUNT: u64 = 2;
static T0_FILE: &str = "layout_test";
static T0_INDEX: u64 = 1;
#[test]
fn test0_write_read_attributes() -> Result<()> {
let pool = lustre_pool();
let l = Layout::new();
l.stripe_size(T0_SIZE)?
.stripe_count(T0_COUNT)?
.ost_index(0, T0_INDEX)?
.pool_name(&pool)?;
assert_eq!(l.get_stripe_size()?, T0_SIZE);
assert_eq!(l.get_stripe_count()?, T0_COUNT);
assert_eq!(l.get_pool_name()?, pool);
assert_eq!(l.get_ost_index(0)?, T0_INDEX);
Ok(())
}
fn new_file_path() -> PathBuf {
let mut path =
PathBuf::from(env::var("TEST_DIR").unwrap_or("/mnt/lustre/layout_test".to_string()));
let mut rng = rand::rng();
path.push(format!("{}_{:x}", T0_FILE, rng.random::<u32>()));
path
}
/*
* Returns a new directory path with a unique name based on the prefix.
*/
fn new_dir_path(prefix: &str) -> PathBuf {
let mut path =
PathBuf::from(env::var("TEST_DIR").unwrap_or("/mnt/lustre/layout_test".to_string()));
let mut rng = rand::rng();
path.push(format!("{}_{:x}", prefix, rng.random::<u32>()));
path
}
fn create_t0_file() -> Result<PathBuf> {
let pool = lustre_pool();
let l = Layout::new();
l.stripe_size(T0_SIZE)?
.stripe_count(T0_COUNT)?
.ost_index(0, T0_INDEX)?
.pool_name(&pool)?;
let path = new_file_path();
let _file = l.create(&path)?;
Ok(path)
}
fn lustre_dir() -> PathBuf {
match LustrePath::parse(&env::var("LUSTRE_DIR").unwrap_or("/mnt/lustre".to_string())) {
Ok(x) => x.as_ref().to_path_buf(),
Err(e) => {
panic!("Failed to parse LUSTRE_DIR: {e:?}");
}
}
}
fn lustre_pool() -> String {
std::env::var("POOL").unwrap_or_else(|_| "testpool".to_string())
}
fn lfs_path() -> String {
std::env::var("LFS").unwrap_or_else(|_| "/usr/bin/lfs".to_string())
}
#[test]
fn test1_read_file_by_path() -> Result<()> {
let path = create_t0_file()?;
let v = rustreapi::get_layout(&path, LayoutGetFlags::NONE)?;
let x = v.first().ok_or("Layout not found")?;
insta::assert_debug_snapshot!(x.stripe_count, @"2");
insta::assert_debug_snapshot!(x.stripe_size, @"1048576");
insta::assert_debug_snapshot!(x.pool_name, @r#"
Some(
"testp",
)
"#);
insta::assert_debug_snapshot!(x.osts[0], @"1");
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test2_read_file_by_fd() -> Result<()> {
let path = create_t0_file()?;
let file = fs::File::open(&path)?;
let v = rustreapi::get_layout_fd(&file, LayoutGetFlags::NONE)?;
let x = v.first().ok_or("Layout not found")?;
insta::assert_debug_snapshot!(x.stripe_count, @"2");
insta::assert_debug_snapshot!(x.stripe_size, @"1048576");
insta::assert_debug_snapshot!(x.pool_name, @r#"
Some(
"testp",
)
"#);
insta::assert_debug_snapshot!(x.osts[0], @"1");
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test3_read_file_by_fid() -> Result<()> {
let lustre_dir = lustre_dir();
let path = create_t0_file()?;
let fid = Fid::with_path(&path)?;
let v = rustreapi::get_layout_fid(&lustre_dir, fid, LayoutGetFlags::NONE)?;
let x = v.first().ok_or("Layout not found")?;
insta::assert_debug_snapshot!(x.stripe_count, @"2");
insta::assert_debug_snapshot!(x.stripe_size, @"1048576");
insta::assert_debug_snapshot!(x.pool_name, @r#"
Some(
"testp",
)
"#);
insta::assert_debug_snapshot!(x.osts[0], @"1");
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test4_verify_compat_with_lfs_setstripe() -> Result<()> {
let pool = std::env::var("POOL").unwrap_or("testpool".to_string());
let path = new_file_path();
let output = Command::new("lfs")
.arg("setstripe")
.arg("-c")
.arg("2")
.arg("-S")
.arg("2M")
.arg("-i")
.arg("1")
.arg("-p")
.arg(&pool)
.arg(&path)
.output()?;
assert!(output.status.success());
let v = rustreapi::get_layout(&path, LayoutGetFlags::NONE)?;
let x = v.first().ok_or("Layout not found")?;
insta::assert_debug_snapshot!(x.stripe_count, @"2");
insta::assert_debug_snapshot!(x.stripe_size, @"2097152");
insta::assert_debug_snapshot!(x.pool_name, @r#"
Some(
"testp",
)
"#);
insta::assert_debug_snapshot!(x.osts[0], @"1");
fs::remove_file(path)?;
Ok(())
}
#[test]
fn test5_file_not_exist() {
let path = new_file_path();
match rustreapi::get_layout(&path, LayoutGetFlags::NONE) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::ENOENT)
}
e => panic!("expected ENOENT, got {:?}", e),
}
}
#[test]
fn test7_get_path_access_error() {
// need to seteuid() to a non-root user
// initial attempt at this didn't work, lookup by path succeded after seteduid().
}
#[test]
fn test8_default_layout_for_generic_file() -> Result<()> {
let path = new_file_path();
let file = fs::File::create(&path)?;
drop(file);
let default_stripe = if cfg!(feature = "LUSTRE_2_17") {
4 * 1024 * 1024
} else {
1024 * 1024
};
let l = rustreapi::Layout::with_path(&path, LayoutGetFlags::NONE)?;
assert_eq!(l.get_stripe_count().ok(), Some(1));
assert_eq!(l.get_stripe_size().ok(), Some(default_stripe));
assert_eq!(l.get_pool_name().ok(), Some(String::from("")));
assert_eq!(l.get_stripe_pattern().ok(), Some(0));
fs::remove_file(path)?;
Ok(())
}
#[test]
fn test9_verify_pattern_errors() -> Result<()> {
let l = Layout::new();
match l.stripe_pattern(lustreapi_sys::LLAPI_LAYOUT_INVALID) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EOPNOTSUPP)
}
e => panic!("expected EOPNOTSUPP, got {:?}", e),
}
let result = l.stripe_pattern(LLAPI_LAYOUT_DEFAULT);
assert!(result.is_ok());
let result = l.stripe_pattern(u64::from(lustreapi_sys::LLAPI_LAYOUT_MDT));
assert!(result.is_ok());
let result = l.stripe_pattern(u64::from(lustreapi_sys::LLAPI_LAYOUT_RAID0));
assert!(result.is_ok());
Ok(())
}
#[test]
fn test10_verify_stripe_count_errors() {
let l = Layout::new();
match l.stripe_count(lustreapi_sys::LLAPI_LAYOUT_INVALID) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
match l.stripe_count(u64::from(lustreapi_sys::LOV_MAX_STRIPE_COUNT) + 1) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
}
#[test]
fn test11_verify_stripe_size_errors() {
let l = Layout::new();
match l.stripe_size(lustreapi_sys::LLAPI_LAYOUT_INVALID) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
// why isn't this defined?
const MAX_STRIPE_SIZE: u64 = 1 << 32;
match l.stripe_size(MAX_STRIPE_SIZE + 1) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
match l.stripe_size(1024 * 1024 - 1) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
match l.stripe_size(1024) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
}
#[test]
fn test12_verify_pool_name_errors() {
let l = Layout::new();
match l.pool_name("0123456789abdefg") {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
}
#[test]
fn test13_ost_index_errors() -> Result<()> {
let l = Layout::new();
let count = 2;
match l.ost_index(0, lustreapi_sys::LLAPI_LAYOUT_INVALID) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
match l.ost_index(0, 1000000) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
let l = Layout::new();
let result = l.stripe_count(count);
assert!(result.is_ok());
match l.get_ost_index(0) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
let l = Layout::new();
let path = new_file_path();
l.stripe_count(count)?;
let file = l.create(&path)?;
drop(file);
let l = Layout::with_path(&path, LayoutGetFlags::NONE)?;
let result = l.get_ost_index(0);
assert!(result.is_ok());
match l.get_ost_index(count + 1) {
Err(rustreapi::Error::Errno(err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test14_file_create_errors() -> Result<()> {
let l = Layout::new();
let lustre_dir = lustre_dir();
match l.create(&lustre_dir) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EEXIST)
}
e => panic!("expected EEXIST, got {:?}", e),
}
match l.create(&PathBuf::from("/tmp/not-lustre.txt")) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::ENOTTY)
}
e => panic!("expected ENOTTY, got {:?}", e),
}
fs::remove_file("/tmp/not-lustre.txt")?;
Ok(())
}
#[test]
fn test15_cant_change_existing() -> Result<()> {
let l = Layout::new();
let path = new_file_path();
let file = l.stripe_count(2)?.create(&path)?;
drop(file);
let l = Layout::new();
let file = l.stripe_count(1)?.open(&path)?;
drop(file);
let l = Layout::with_path(&path, LayoutGetFlags::NONE)?;
assert_eq!(l.get_stripe_count()?, 2);
Ok(())
}
#[test]
fn test15b_can_change_existing() -> Result<()> {
let path = new_file_path();
let _file = OpenOptions::new()
.write(true)
.create_new(true)
.lov_delay(true)
.open(&path)?;
let file = Layout::new()
.stripe_count(3)?
.stripe_size(512 * 1024)?
.open(&path)?;
drop(file);
let l = Layout::with_path(&path, LayoutGetFlags::NONE)?;
assert_eq!(l.get_stripe_count()?, 3);
assert_eq!(l.get_stripe_size()?, 512 * 1024);
Ok(())
}
// this test is similar to test_default_values as we are not testing NULL layout,
#[test]
fn test16_default_stripe_attributes_applied() -> Result<()> {
let deflayout = Layout::new();
let stripe_size = Layout::get_stripe_size(&deflayout)?;
assert_eq!(
stripe_size, LLAPI_LAYOUT_DEFAULT,
"Failed to get stripe size for default"
);
let stripe_count = Layout::get_stripe_count(&deflayout)?;
assert_eq!(
stripe_count, LLAPI_LAYOUT_DEFAULT,
"Failed to get stripe count for default"
);
drop(deflayout);
Ok(())
}
/* needs version of Lustre containing LU-18972 to be able to run this test. */
#[test]
#[cfg(feature = "LUSTRE_2_16")]
fn test17_layout_wide_uses_all_osts() -> Result<()> {
let path = new_file_path();
let layout = Layout::new();
layout.stripe_count(unsafe { llapi_LAYOUT_WIDE_MIN() })?;
let file = layout.create(&path)?;
drop(file);
let file = layout.open(&path)?;
let ost_count_all = ost_count(&LustrePath::parse("/mnt/lustre")?)?;
drop(file);
let file_layout = Layout::with_path(&path, LayoutGetFlags::EXPECTED)?;
let stripe_count = file_layout.get_stripe_count()?;
assert!(
stripe_count.abs_diff(ost_count_all as u64) <= 1,
"stripe_count {} differs from ost_count {} by more than 1",
stripe_count,
ost_count_all
);
Ok(())
}
#[test]
fn test18_pool_name_notation() -> Result<()> {
let path = new_file_path();
let poolname = lustre_pool();
let pool_with_fsname = format!("lustre.{}", poolname);
let layout = Layout::new();
layout.pool_name(&pool_with_fsname)?;
// Verify the pool name was set correctly (should strip fsname prefix)
let retrieved_pool = layout.get_pool_name()?;
assert_eq!(
retrieved_pool, poolname,
"Pool name mismatch: {} != {}",
retrieved_pool, poolname
);
let file = layout.create(&path)?;
drop(file);
let file_layout = Layout::with_path(&path, LayoutGetFlags::NONE)?;
let file_pool = file_layout.get_pool_name()?;
assert_eq!(
file_pool, poolname,
"File pool name mismatch: {} != {}",
file_pool, poolname
);
Ok(())
}
#[test]
fn test22_file_create_mode_correctly_applied() -> Result<()> {
let path = new_file_path();
let mode_in = 0o640;
// Save original umask and set restrictive umask
let original_umask = unsafe { libc::umask(0o022) };
let layout = Layout::new();
let file = layout.create_with_mode(&path, mode_in)?;
unsafe { libc::umask(original_umask) };
let metadata = file.metadata()?;
let mode_out = metadata.mode() & 0o7777; // (equivalent to ~S_IFMT)
drop(file);
assert_eq!(
mode_in, mode_out as i32,
"Mode mismatch: expected 0o{:o}, got 0o{:o}",
mode_in, mode_out
);
Ok(())
}
#[test]
fn test24_layout_get_expected_works_with_existing_file() -> Result<()> {
let path = new_file_path();
//(equivalent to open() with O_CREAT)
let file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.mode(0o640)
.open(&path)?;
drop(file);
let layout = Layout::with_path(&path, LayoutGetFlags::EXPECTED)?;
let count = layout.get_stripe_count()?;
assert_ne!(
count, LLAPI_LAYOUT_DEFAULT,
"Expected literal stripe count value, got default"
);
let size = layout.get_stripe_size()?;
assert_ne!(
size, LLAPI_LAYOUT_DEFAULT,
"Expected literal stripe size value, got default"
);
let pattern = layout.get_stripe_pattern()?;
assert_ne!(
pattern, LLAPI_LAYOUT_DEFAULT,
"Expected literal stripe pattern value, got default"
);
Ok(())
}
#[test]
fn test26_layout_get_expected_partially_specified_parent() -> Result<()> {
const T26_STRIPE_SIZE: u64 = 1048576 * 4; // 4MB
let dir = new_dir_path("test26");
std::fs::create_dir_all(&dir)?;
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o750))?;
// could be problematic on test systems if lfs is defined elsewhere.
let output = Command::new(&lfs_path())
.args(&[
"setstripe",
"-S",
&T26_STRIPE_SIZE.to_string(),
dir.to_str().unwrap(),
])
.output()?;
assert!(
output.status.success(),
"lfs setstripe command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
// Get layout with EXPECTED flag - should combine specified stripe size with defaults
let layout = Layout::with_path(&dir, LayoutGetFlags::EXPECTED)?;
let count = layout.get_stripe_count()?;
assert_ne!(
count, LLAPI_LAYOUT_DEFAULT,
"Expected literal stripe count value, got default"
);
// Verify stripe size matches what we set
let size = layout.get_stripe_size()?;
assert_eq!(
size, T26_STRIPE_SIZE,
"Expected stripe size {}, got {}",
T26_STRIPE_SIZE, size
);
let pattern = layout.get_stripe_pattern()?;
assert_ne!(
pattern, LLAPI_LAYOUT_DEFAULT,
"Expected literal stripe pattern value, got default"
);
Ok(())
}
/* llapi_layout_stripe_count_get returns LLAPI_LAYOUT_WIDE for a directory
* with a stripe_count of -1.
*/
#[test]
#[cfg(feature = "LUSTRE_2_16")]
fn test28_layout_wide_stripe_count() -> Result<()> {
let dir = new_dir_path("test28");
std::fs::create_dir_all(&dir)?;
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o750))?;
let output = Command::new(&lfs_path())
.args(&["setstripe", "-c", "-1", dir.to_str().unwrap()])
.output()?;
assert!(
output.status.success(),
"lfs setstripe command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let layout = Layout::with_path(&dir, LayoutGetFlags::NONE)?;
let count = layout.get_stripe_count()?;
let expected_wide = unsafe { llapi_LAYOUT_WIDE_MIN() };
assert_eq!(
count, expected_wide,
"Expected LLAPI_LAYOUT_WIDE ({}), got count = {}",
expected_wide, count
);
Ok(())
}
#[test]
fn test30_composite_file_traverse() -> Result<()> {
let end: [u64; 3] = [
64 * 1024 * 1024,
1024 * 1024 * 1024,
lustreapi_sys::LUSTRE_EOF as u64,
];
let start: [u64; 3] = [0, end[0], end[1]];
let path = new_file_path();
let l = Layout::new();
l.stripe_count(1)?;
// attempt to add component without extent will fail
match l.comp_add() {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
l.comp_extent(start[0], end[0])?
.comp_add()?
.comp_extent(start[1], end[1])?
.comp_add()?
.comp_extent(start[2], end[2])?;
let file = l.create(&path)?;
drop(file);
let l = Layout::with_path(&path, LayoutGetFlags::NONE)?;
let (s, e) = l.get_comp_extent()?;
assert_eq!(s, start[2]);
assert_eq!(e, end[2]);
l.comp_use(CompUse::First)?;
// attempt to delete non-tail component will fail
match l.comp_del() {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
let (s, e) = l.get_comp_extent()?;
assert_eq!(s, start[0]);
assert_eq!(e, end[0]);
l.comp_use(CompUse::Next)?;
assert_eq!(l.get_comp_extent()?, (start[1], end[1]));
l.comp_use(CompUse::Next)?;
assert_eq!(l.get_comp_extent()?, (start[2], end[2]));
let result = l.comp_del();
assert!(result.is_ok());
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test31_manipulate_components_on_file() -> Result<()> {
let end: [u64; 3] = [
64 * 1024 * 1024,
1024 * 1024 * 1024,
lustreapi_sys::LUSTRE_EOF as u64,
];
let start: [u64; 3] = [0, end[0], end[1]];
let path = new_file_path();
let file = Layout::new()
.stripe_count(1)?
.comp_extent(start[0], end[0])?
.create(&path)?;
drop(file);
Layout::new()
.stripe_count(2)?
.comp_extent(start[1], end[1])?
.file_comp_add(&path)?;
let layout = Layout::with_path(&path, LayoutGetFlags::NONE)?;
let mut i = 0;
let mut id: [u32; 2] = [0; 2];
for l in layout.iter() {
assert_eq!(l.get_comp_extent()?, (start[i], end[i]));
id[i] = l.get_comp_id()?;
i += 1;
}
for l in layout.iter_reverse() {
i -= 1;
assert_eq!(l.get_comp_extent()?, (start[i], end[i]));
assert_eq!(l.get_comp_id()?, id[i]);
}
match Layout::file_comp_del(&path, id[0]) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
let result = Layout::file_comp_del(&path, id[1]);
assert!(result.is_ok());
Ok(())
}
#[test]
fn test34_extending_layouts() -> Result<()> {
let path = new_file_path();
let end: [u64; 4] = [
10 << 20,
1 << 30,
10 << 30,
lustreapi_sys::LUSTRE_EOF as u64,
];
let start: [u64; 4] = [0, end[0], end[1], end[2]];
let layout = Layout::new();
layout
// Comp 0
.stripe_count(1)?
.comp_extent(start[0], end[0])?
// Comp 1
.comp_add()?
.stripe_count(1)?
.comp_extent(start[1], end[1])?
.comp_flags(CompEntryFlags::Extension)?;
match layout.extension_size(32 << 20) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
match layout.extension_size(5 << 40) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
layout.extension_size(64 << 20)?;
// Broken Comp 2
layout
.comp_add()?
.comp_extent(start[2], end[2])?
// this flag is invalid but can't be checked until file is created
.comp_flags(CompEntryFlags::Extension)?;
match layout.create(&path) {
Err(rustreapi::Error::MsgErrno(_, err)) => {
assert_eq!(err, nix::errno::Errno::EINVAL)
}
e => panic!("expected EINVAL, got {:?}", e),
}
layout.comp_del()?;
// Zero-length Comp 2
layout
.comp_add()?
.comp_extent(start[2], start[2])?
// Extendable Comp 3
.comp_add()?
.comp_extent(start[2], end[3])?
.comp_flags(CompEntryFlags::Extension)?;
let result = layout.create(&path);
assert!(result.is_ok());
let layout = Layout::with_path(&path, LayoutGetFlags::NONE)?;
layout.sanity(false, false)?;
Ok(())
}
#[test]
fn test35_all_paths_at_single_file() -> Result<()> {
use std::fs::File;
let path = create_t0_file()?;
let lustre_path = lustre_dir();
let mount_file = File::open(&lustre_path)?;
let test_file = File::open(&path)?;
let fid = Fid::with_fd(&test_file)?;
let all_paths = fid.all_paths_at(&mount_file)?;
assert_eq!(
all_paths.len(),
1,
"Should have exactly one path for single file"
);
assert_eq!(
all_paths[0].file_name(),
path.file_name(),
"Returned filename should match expected"
);
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test36_all_paths_convenience_method() -> Result<()> {
let path = create_t0_file()?;
let lustre_path = lustre_dir();
let mount_path = LustrePath::parse(lustre_path.to_str().unwrap())?;
let test_file = std::fs::File::open(&path)?;
let fid = Fid::with_fd(&test_file)?;
let all_paths = fid.all_paths(&mount_path)?;
assert!(!all_paths.is_empty(), "Should have at least one path");
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test37_all_paths_consistency_with_path_at() -> Result<()> {
use std::fs::File;
let path = create_t0_file()?;
let lustre_path = lustre_dir();
let mount_file = File::open(&lustre_path)?;
let test_file = File::open(&path)?;
let fid = Fid::with_fd(&test_file)?;
let single_path = fid.path_at(&mount_file)?;
let all_paths = fid.all_paths_at(&mount_file)?;
assert!(
!all_paths.is_empty(),
"all_paths should return at least one path"
);
assert_eq!(
single_path, all_paths[0],
"First path from all_paths_at should match path_at result"
);
fs::remove_file(&path)?;
Ok(())
}
#[test]
fn test38_all_paths_with_hard_links() -> Result<()> {
use std::fs::{File, hard_link};
let original_path = create_t0_file()?;
let lustre_path = lustre_dir();
let mount_file = File::open(&lustre_path)?;
// Create two hard links to the original file
let link1_path = new_file_path();
let link2_path = new_file_path();
hard_link(&original_path, &link1_path)?;
hard_link(&original_path, &link2_path)?;
let test_file = File::open(&original_path)?;
let fid = Fid::with_fd(&test_file)?;
let all_paths = fid.all_paths_at(&mount_file)?;
assert_eq!(
all_paths.len(),
3,
"Should have exactly three paths for file with two hard links"
);
// Extract filenames from all paths
let mut returned_filenames: Vec<_> = all_paths.iter().map(|p| p.file_name()).collect();
returned_filenames.sort();
let mut expected_filenames = vec![
original_path.file_name(),
link1_path.file_name(),
link2_path.file_name(),
];
expected_filenames.sort();
assert_eq!(
returned_filenames, expected_filenames,
"All hard link paths should be returned"
);
// Clean up all files
fs::remove_file(&original_path)?;
fs::remove_file(&link1_path)?;
fs::remove_file(&link2_path)?;
Ok(())
}