Fix code quality: trailing whitespace, unused imports, clippy warnings
- Fix trailing whitespace in kex.rs and s3.rs - Add missing KexProposal import in kex_complete.rs - Auto-fix clippy warnings across all crates - All 153 tests pass
This commit is contained in:
336
tests/s3_vfs_test.rs
Normal file
336
tests/s3_vfs_test.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Helper to create an S3Vfs instance pointing to local MinIO.
|
||||
fn make_s3_vfs() -> markbase_core::vfs::s3_fs::S3Vfs {
|
||||
markbase_core::vfs::s3_fs::S3Vfs::new(
|
||||
"http://127.0.0.1:9000",
|
||||
"us-east-1",
|
||||
"test-bucket",
|
||||
"minioadmin",
|
||||
"minioadmin",
|
||||
)
|
||||
.expect("Failed to create S3Vfs")
|
||||
}
|
||||
|
||||
/// Helper to check if MinIO is reachable (skip test if not).
|
||||
fn minio_reachable() -> bool {
|
||||
use std::net::TcpStream;
|
||||
TcpStream::connect_timeout(
|
||||
&"127.0.0.1:9000".parse().unwrap(),
|
||||
Duration::from_secs(2),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn cleanup(vfs: &impl markbase_core::vfs::VfsBackend, path: &Path) {
|
||||
if vfs.exists(path) {
|
||||
let _ = vfs.remove_file(path);
|
||||
}
|
||||
// Try parent dirs
|
||||
let mut p = path.parent();
|
||||
while let Some(dir) = p {
|
||||
if dir == Path::new("/") {
|
||||
break;
|
||||
}
|
||||
let dk = format!("{}/", dir.display());
|
||||
let _ = vfs.remove_dir(Path::new(&dk));
|
||||
p = dir.parent();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dir() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_create_dir");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let dir = Path::new("/test-create-dir/");
|
||||
|
||||
if vfs.exists(dir) {
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
assert!(!vfs.exists(dir));
|
||||
vfs.create_dir(dir, 0o755).expect("create_dir failed");
|
||||
assert!(vfs.exists(dir));
|
||||
|
||||
let stat = vfs.stat(dir).expect("stat failed");
|
||||
assert!(stat.is_dir);
|
||||
|
||||
vfs.remove_dir(dir).expect("remove_dir failed");
|
||||
assert!(!vfs.exists(dir));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dir_all() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_create_dir_all");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let nested = Path::new("/test-nested/a/b/c/");
|
||||
|
||||
// Clean up from previous runs
|
||||
for d in [Path::new("/test-nested/a/b/c/"), Path::new("/test-nested/a/b/"), Path::new("/test-nested/a/"), Path::new("/test-nested/")] {
|
||||
if vfs.exists(d) {
|
||||
vfs.remove_dir(d).ok();
|
||||
}
|
||||
}
|
||||
|
||||
vfs.create_dir_all(nested, 0o755).expect("create_dir_all failed");
|
||||
|
||||
assert!(vfs.exists(Path::new("/test-nested/")));
|
||||
assert!(vfs.exists(Path::new("/test-nested/a/")));
|
||||
assert!(vfs.exists(Path::new("/test-nested/a/b/")));
|
||||
assert!(vfs.exists(Path::new("/test-nested/a/b/c/")));
|
||||
|
||||
for d in [Path::new("/test-nested/a/b/c/"), Path::new("/test-nested/a/b/"), Path::new("/test-nested/a/"), Path::new("/test-nested/")] {
|
||||
vfs.remove_dir(d).expect("cleanup remove_dir failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_and_read_file() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_write_and_read_file");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let file_path = Path::new("/test-write-read.txt");
|
||||
cleanup(&vfs, file_path);
|
||||
|
||||
use markbase_core::vfs::open_flags::OpenFlags;
|
||||
|
||||
// Write
|
||||
let mut f = vfs
|
||||
.open_file(file_path, &OpenFlags::write())
|
||||
.expect("open for write failed");
|
||||
let content = b"Hello S3Vfs! This is a test.";
|
||||
f.write_all(content).expect("write_all failed");
|
||||
f.flush().expect("flush failed");
|
||||
|
||||
// Stat
|
||||
let stat = vfs.stat(file_path).expect("stat failed");
|
||||
assert_eq!(stat.size, content.len() as u64);
|
||||
assert!(!stat.is_dir);
|
||||
|
||||
// Read
|
||||
let mut f = vfs
|
||||
.open_file(file_path, &OpenFlags::read())
|
||||
.expect("open for read failed");
|
||||
let mut buf = vec![0u8; content.len()];
|
||||
f.read_exact(&mut buf).expect("read_exact failed");
|
||||
assert_eq!(buf, content);
|
||||
|
||||
// Exists
|
||||
assert!(vfs.exists(file_path));
|
||||
|
||||
// Cleanup
|
||||
vfs.remove_file(file_path).expect("remove_file failed");
|
||||
assert!(!vfs.exists(file_path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_dir() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_read_dir");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let dir = Path::new("/test-readdir/");
|
||||
let file1 = Path::new("/test-readdir/file1.txt");
|
||||
let file2 = Path::new("/test-readdir/file2.txt");
|
||||
let subdir = Path::new("/test-readdir/sub/");
|
||||
|
||||
// Clean
|
||||
for p in &[file1, file2] {
|
||||
cleanup(&vfs, p);
|
||||
}
|
||||
if vfs.exists(subdir) {
|
||||
vfs.remove_dir(subdir).ok();
|
||||
}
|
||||
if vfs.exists(dir) {
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
// Create structure
|
||||
vfs.create_dir(dir, 0o755).expect("create_dir failed");
|
||||
|
||||
let content = b"data";
|
||||
let flags = markbase_core::vfs::open_flags::OpenFlags::write();
|
||||
for p in &[file1, file2] {
|
||||
let mut f = vfs.open_file(p, &flags).expect("open for write failed");
|
||||
f.write_all(content).expect("write_all failed");
|
||||
f.flush().expect("flush failed");
|
||||
}
|
||||
vfs.create_dir(subdir, 0o755).expect("create subdir failed");
|
||||
|
||||
// Read directory
|
||||
let entries = vfs.read_dir(dir).expect("read_dir failed");
|
||||
let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
|
||||
assert!(names.contains(&"file1.txt"), "missing file1.txt: {:?}", names);
|
||||
assert!(names.contains(&"file2.txt"), "missing file2.txt: {:?}", names);
|
||||
assert!(names.contains(&"sub"), "missing sub: {:?}", names);
|
||||
|
||||
// Check entry types
|
||||
for entry in &entries {
|
||||
if entry.name == "file1.txt" || entry.name == "file2.txt" {
|
||||
assert!(!entry.stat.is_dir, "{} should be a file", entry.name);
|
||||
}
|
||||
if entry.name == "sub" {
|
||||
assert!(entry.stat.is_dir, "{} should be a dir", entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
vfs.remove_file(file1).ok();
|
||||
vfs.remove_file(file2).ok();
|
||||
vfs.remove_dir(subdir).ok();
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_rename");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let from = Path::new("/test-rename-src.txt");
|
||||
let to = Path::new("/test-rename-dst.txt");
|
||||
cleanup(&vfs, from);
|
||||
cleanup(&vfs, to);
|
||||
|
||||
let content = b"rename test data";
|
||||
let flags = markbase_core::vfs::open_flags::OpenFlags::write();
|
||||
{
|
||||
let mut f = vfs.open_file(from, &flags).expect("open for write");
|
||||
f.write_all(content).expect("write_all");
|
||||
f.flush().expect("flush");
|
||||
}
|
||||
assert!(vfs.exists(from));
|
||||
|
||||
vfs.rename(from, to).expect("rename failed");
|
||||
assert!(!vfs.exists(from));
|
||||
assert!(vfs.exists(to));
|
||||
|
||||
// Verify content
|
||||
let mut f = vfs
|
||||
.open_file(to, &markbase_core::vfs::open_flags::OpenFlags::read())
|
||||
.expect("open for read");
|
||||
let mut buf = vec![0u8; content.len()];
|
||||
f.read_exact(&mut buf).expect("read_exact");
|
||||
assert_eq!(buf, content);
|
||||
|
||||
vfs.remove_file(to).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hard_link() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_hard_link");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let original = Path::new("/test-link-orig.txt");
|
||||
let link = Path::new("/test-link-copy.txt");
|
||||
cleanup(&vfs, original);
|
||||
cleanup(&vfs, link);
|
||||
|
||||
let content = b"hard link test";
|
||||
let flags = markbase_core::vfs::open_flags::OpenFlags::write();
|
||||
{
|
||||
let mut f = vfs.open_file(original, &flags).expect("open for write");
|
||||
f.write_all(content).expect("write_all");
|
||||
f.flush().expect("flush");
|
||||
}
|
||||
|
||||
vfs.hard_link(original, link).expect("hard_link failed");
|
||||
assert!(vfs.exists(original));
|
||||
assert!(vfs.exists(link));
|
||||
|
||||
// Verify link content
|
||||
let mut f = vfs
|
||||
.open_file(link, &markbase_core::vfs::open_flags::OpenFlags::read())
|
||||
.expect("open for read");
|
||||
let mut buf = vec![0u8; content.len()];
|
||||
f.read_exact(&mut buf).expect("read_exact");
|
||||
assert_eq!(buf, content);
|
||||
|
||||
vfs.remove_file(original).ok();
|
||||
vfs.remove_file(link).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dir_not_empty() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_remove_dir_not_empty");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let dir = Path::new("/test-nonempty/");
|
||||
let file = Path::new("/test-nonempty/file.txt");
|
||||
cleanup(&vfs, file);
|
||||
if vfs.exists(dir) {
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
vfs.create_dir(dir, 0o755).expect("create_dir");
|
||||
let flags = markbase_core::vfs::open_flags::OpenFlags::write();
|
||||
{
|
||||
let mut f = vfs.open_file(file, &flags).expect("open for write");
|
||||
f.write_all(b"x").expect("write");
|
||||
f.flush().expect("flush");
|
||||
}
|
||||
|
||||
match vfs.remove_dir(dir) {
|
||||
Err(markbase_core::vfs::VfsError::NotEmpty(_)) => { /* expected */ }
|
||||
other => panic!("Expected NotEmpty, got {:?}", other),
|
||||
}
|
||||
|
||||
vfs.remove_file(file).ok();
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_real_path() {
|
||||
if !minio_reachable() {
|
||||
eprintln!("MinIO not reachable, skipping test_real_path");
|
||||
return;
|
||||
}
|
||||
let vfs = make_s3_vfs();
|
||||
let dir = Path::new("/test-realpath/");
|
||||
let file = Path::new("/test-realpath/foo.txt");
|
||||
|
||||
cleanup(&vfs, file);
|
||||
if vfs.exists(dir) {
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
|
||||
vfs.create_dir(dir, 0o755).expect("create_dir");
|
||||
let flags = markbase_core::vfs::open_flags::OpenFlags::write();
|
||||
{
|
||||
let mut f = vfs.open_file(file, &flags).expect("open for write");
|
||||
f.write_all(b"test").expect("write");
|
||||
f.flush().expect("flush");
|
||||
}
|
||||
|
||||
let rp = vfs.real_path(dir).expect("real_path dir");
|
||||
assert!(
|
||||
rp.to_string_lossy().ends_with("test-realpath"),
|
||||
"real_path dir: {:?}",
|
||||
rp
|
||||
);
|
||||
|
||||
let rp = vfs.real_path(file).expect("real_path file");
|
||||
assert!(
|
||||
rp.to_string_lossy().ends_with("test-realpath/foo.txt"),
|
||||
"real_path file: {:?}",
|
||||
rp
|
||||
);
|
||||
|
||||
vfs.remove_file(file).ok();
|
||||
vfs.remove_dir(dir).ok();
|
||||
}
|
||||
145
tests/ssh_full_integration.sh
Executable file
145
tests/ssh_full_integration.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/bin/bash
|
||||
# SSH/SFTP/SCP/rsync 完整整合測試 - 自動化執行腳本
|
||||
# 用法: bash tests/ssh_full_integration.sh
|
||||
|
||||
set -e
|
||||
|
||||
SERVER_PORT=2024
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
RSYNC_RSH="ssh -p $SERVER_PORT $SSH_OPTS"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
log() { echo -e "\n\033[36m=== $1 ===\033[0m"; }
|
||||
pass() { echo -e " \033[32m✅ $1\033[0m"; PASS=$((PASS+1)); }
|
||||
fail() { echo -e " \033[31m❌ $1\033[0m"; FAIL=$((FAIL+1)); }
|
||||
|
||||
cleanup() {
|
||||
rm -f /tmp/sftp_test_*.bin /tmp/scp_*.bin /tmp/rsync_*.bin /tmp/batch_*.bin
|
||||
rm -rf /tmp/rsync_dir* /tmp/scp_dir* /tmp/parallel_*
|
||||
}
|
||||
|
||||
# ── 建立測試檔案 ──
|
||||
log "建立測試檔案"
|
||||
dd if=/dev/urandom of=/tmp/sftp_test_2m.bin bs=1M count=2 2>/dev/null
|
||||
dd if=/dev/urandom of=/tmp/sftp_test_5m.bin bs=1M count=5 2>/dev/null
|
||||
SRC_MD5=$(md5 /tmp/sftp_test_5m.bin | awk '{print $NF}')
|
||||
echo " 5MB source MD5: $SRC_MD5"
|
||||
|
||||
# ── 1. SSH 連線 ──
|
||||
log "1. SSH Basic Connection"
|
||||
if timeout 10 ssh -p $SERVER_PORT $SSH_OPTS demo@127.0.0.1 'echo "SSH_OK"' 2>/dev/null | grep -q SSH_OK; then
|
||||
pass "SSH connection + auth"
|
||||
else
|
||||
fail "SSH connection"
|
||||
fi
|
||||
|
||||
# ── 2. SFTP 操作 ──
|
||||
log "2. SFTP Upload + Download"
|
||||
timeout 60 sftp -P $SERVER_PORT $SSH_OPTS demo@127.0.0.1 << EOF 2>/dev/null >/dev/null
|
||||
put /tmp/sftp_test_5m.bin sftp_5m.bin
|
||||
get sftp_5m.bin /tmp/sftp_test_5m_dl.bin
|
||||
rm sftp_5m.bin
|
||||
bye
|
||||
EOF
|
||||
|
||||
DL_MD5=$(md5 /tmp/sftp_test_5m_dl.bin 2>/dev/null | awk '{print $NF}')
|
||||
if [ "$SRC_MD5" = "$DL_MD5" ]; then
|
||||
pass "SFTP 5MB (MD5: $SRC_MD5)"
|
||||
else
|
||||
fail "SFTP 5MB MD5 mismatch: src=$SRC_MD5 dl=$DL_MD5"
|
||||
fi
|
||||
|
||||
# ── 3. SFTP Enterprise Errors ──
|
||||
log "3. SFTP Error Handling"
|
||||
ERRORS=$(timeout 10 sftp -P $SERVER_PORT $SSH_OPTS demo@127.0.0.1 << 'EOF' 2>&1
|
||||
mkdir /etc/forbidden
|
||||
stat /nonexistent
|
||||
bye
|
||||
EOF
|
||||
)
|
||||
if echo "$ERRORS" | grep -iq "permission denied"; then
|
||||
pass "SFTP Permission denied"
|
||||
else
|
||||
fail "SFTP Permission denied missing"
|
||||
fi
|
||||
if echo "$ERRORS" | grep -iq "no such file"; then
|
||||
pass "SFTP No such file"
|
||||
else
|
||||
fail "SFTP No such file missing"
|
||||
fi
|
||||
|
||||
# ── 4. SFTP Batch Files ──
|
||||
log "4. SFTP Batch Transfer"
|
||||
for i in 1 2 3 4 5; do
|
||||
dd if=/dev/urandom of=/tmp/batch_${i}.bin bs=1K count=64 2>/dev/null
|
||||
done
|
||||
|
||||
timeout 60 sftp -P $SERVER_PORT $SSH_OPTS demo@127.0.0.1 << EOF 2>/dev/null >/dev/null
|
||||
lcd /tmp
|
||||
put batch_1.bin batch_2.bin batch_3.bin batch_4.bin batch_5.bin
|
||||
get batch_1.bin batch_2.bin batch_3.bin batch_4.bin batch_5.bin
|
||||
rm batch_1.bin batch_2.bin batch_3.bin batch_4.bin batch_5.bin
|
||||
bye
|
||||
EOF
|
||||
|
||||
ALL_OK=true
|
||||
for i in 1 2 3 4 5; do
|
||||
S=$(md5 /tmp/batch_${i}.bin | awk '{print $NF}')
|
||||
D=$(md5 /tmp/batch_${i}.bin 2>/dev/null | awk '{print $NF}')
|
||||
[ "$S" != "$D" ] && ALL_OK=false
|
||||
done
|
||||
$ALL_OK && pass "SFTP Batch (5 files)" || fail "SFTP Batch MD5 mismatch"
|
||||
|
||||
# ── 5. SCP ──
|
||||
log "5. SCP Transfer"
|
||||
timeout 30 scp -P $SERVER_PORT $SSH_OPTS /tmp/sftp_test_5m.bin demo@127.0.0.1:scp_test.bin 2>/dev/null
|
||||
timeout 30 scp -P $SERVER_PORT $SSH_OPTS demo@127.0.0.1:scp_test.bin /tmp/scp_dl.bin 2>/dev/null
|
||||
|
||||
SCP_MD5=$(md5 /tmp/scp_dl.bin 2>/dev/null | awk '{print $NF}')
|
||||
if [ "$SRC_MD5" = "$SCP_MD5" ]; then
|
||||
pass "SCP 5MB (MD5: $SCP_MD5)"
|
||||
else
|
||||
fail "SCP MD5 mismatch"
|
||||
fi
|
||||
|
||||
# ── 6. rsync ──
|
||||
log "6. rsync Transfer"
|
||||
timeout 30 rsync -avz --rsh="$RSYNC_RSH" /tmp/sftp_test_5m.bin demo@127.0.0.1:rsync_test.bin 2>/dev/null >/dev/null
|
||||
timeout 30 rsync -avz --rsh="$RSYNC_RSH" demo@127.0.0.1:rsync_test.bin /tmp/rsync_dl.bin 2>/dev/null >/dev/null
|
||||
|
||||
RSYNC_MD5=$(md5 /tmp/rsync_dl.bin 2>/dev/null | awk '{print $NF}')
|
||||
if [ "$SRC_MD5" = "$RSYNC_MD5" ]; then
|
||||
pass "rsync 5MB (MD5: $RSYNC_MD5)"
|
||||
else
|
||||
fail "rsync MD5 mismatch"
|
||||
fi
|
||||
|
||||
# ── 7. Delta rsync ──
|
||||
log "7. rsync Delta Transfer"
|
||||
echo "delta" >> /tmp/sftp_test_5m.bin
|
||||
timeout 30 rsync -avz --rsh="$RSYNC_RSH" /tmp/sftp_test_5m.bin demo@127.0.0.1:rsync_test.bin 2>&1 | grep -q "speedup" && \
|
||||
pass "rsync delta (small change)" || fail "rsync delta"
|
||||
|
||||
# ── 8. Shell Commands ──
|
||||
log "8. SSH Shell Commands"
|
||||
OUT=$(timeout 10 ssh -p $SERVER_PORT $SSH_OPTS demo@127.0.0.1 'echo hello; whoami' 2>/dev/null)
|
||||
echo "$OUT" | grep -q hello && echo "$OUT" | grep -q demo && \
|
||||
pass "SSH shell commands" || fail "SSH shell commands"
|
||||
|
||||
# ── 9. Stress: 10 consecutive connections ──
|
||||
log "9. Stress Test (10 connections)"
|
||||
CONN_OK=0
|
||||
for i in $(seq 1 10); do
|
||||
timeout 10 ssh -p $SERVER_PORT $SSH_OPTS demo@127.0.0.1 'echo OK' 2>/dev/null | grep -q OK && CONN_OK=$((CONN_OK+1))
|
||||
done
|
||||
[ "$CONN_OK" -eq 10 ] && pass "10/10 connections OK" || fail "$CONN_OK/10 connections OK"
|
||||
|
||||
# ── Summary ──
|
||||
log "SUMMARY"
|
||||
echo -e " \033[32mPassed: $PASS\033[0m"
|
||||
echo -e " \033[31mFailed: $FAIL\033[0m"
|
||||
[ "$FAIL" -eq 0 ] && echo -e "\033[32m\n ✅ ALL TESTS PASSED\033[0m" || echo -e "\033[31m\n ❌ SOME TESTS FAILED\033[0m"
|
||||
|
||||
cleanup
|
||||
exit $FAIL
|
||||
Reference in New Issue
Block a user