use crate::config::Config;
use std::path::Path;
use std::time::{Duration, Instant};
fn rust_source(n: usize) -> String {
let mut s = String::with_capacity(n * 32);
for i in 0..n {
s.push_str(&format!(
"fn item_{i}(x: i32) -> i32 {{ let y = x + {i}; y * 2 }} // comment {i}\n"
));
}
s
}
#[test]
#[ignore] fn highlight_lang_large_source_is_bounded() {
let src = rust_source(5000);
let t = Instant::now();
let lines = crate::preview::code::highlight_lang(&src, "rust", "TwoDark");
let dt = t.elapsed();
assert_eq!(lines.len(), 5000, "全行ハイライトされる");
assert!(
dt < Duration::from_secs(10),
"5000 行ハイライトが遅すぎる(回帰?): {dt:?}"
);
}
#[test]
fn warm_then_highlight_is_fast() {
let dir = std::env::temp_dir().join("konoma_speed_warm_test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let f = dir.join("a.rs");
std::fs::write(&f, rust_source(300).as_bytes()).unwrap();
let t = Instant::now();
crate::preview::code::warm_file("rs", &f);
let warm_dt = t.elapsed();
assert!(
warm_dt < Duration::from_secs(5),
"warm_file が遅すぎる: {warm_dt:?}"
);
let src = std::fs::read_to_string(&f).unwrap();
let t = Instant::now();
let lines = crate::preview::code::highlight(&src, &f, "TwoDark");
let hot_dt = t.elapsed();
assert_eq!(lines.len(), 300);
assert!(
hot_dt < Duration::from_secs(2),
"ウォーム後のハイライトが遅すぎる: {hot_dt:?}"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[ignore] fn render_markdown_large_doc_is_bounded() {
let mut md = String::new();
for i in 0..400 {
md.push_str(&format!(
"## Heading {i}\n\nSome *body* text with `code` and a [link](https://x/{i}).\n\n"
));
}
md.push_str("```rust\n");
md.push_str(&rust_source(200));
md.push_str("```\n");
let t = Instant::now();
let lines = crate::preview::markdown::render_markdown(
&md,
80,
crate::preview::markdown::CodeStyle::default(),
"TwoDark",
false,
);
let dt = t.elapsed();
assert!(!lines.is_empty());
assert!(
dt < Duration::from_secs(10),
"大きい md のレンダリングが遅すぎる: {dt:?}"
);
}
#[test]
fn tree_build_and_visible_range_many_entries_is_bounded() {
use ratatui::backend::TestBackend;
use ratatui::Terminal;
let dir = std::env::temp_dir().join("konoma_speed_tree_test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
for i in 0..1000 {
std::fs::write(dir.join(format!("file_{i:04}.txt")), b"x").unwrap();
}
let mut app = crate::app::App::new(dir.clone(), Config::default()).unwrap();
let t = Instant::now();
app.rebuild_tree().unwrap();
let build_dt = t.elapsed();
assert_eq!(app.entries.len(), 1000, "全件ツリー化");
assert!(
build_dt < Duration::from_secs(2),
"1000 件のツリー再構築が遅すぎる: {build_dt:?}"
);
app.selected = app.entries.len() - 1;
let mut term = Terminal::new(TestBackend::new(60, 30)).unwrap();
let t = Instant::now();
term.draw(|f| crate::ui::render(f, &mut app)).unwrap();
let draw_dt = t.elapsed();
assert!(
draw_dt < Duration::from_secs(1),
"可視範囲を含む1描画が遅すぎる: {draw_dt:?}"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn decode_gif_sample_is_bounded() {
let p = Path::new("samples/sample.gif");
if !p.exists() {
return; }
let t = Instant::now();
let frames = crate::preview::image::decode_gif(p);
let dt = t.elapsed();
assert!(
frames.map(|f| f.len() > 1).unwrap_or(false),
"アニメ GIF として展開"
);
assert!(
dt < Duration::from_secs(5),
"sample.gif のデコードが遅すぎる: {dt:?}"
);
}
#[cfg(feature = "git")]
#[test]
#[ignore] fn same_repo_navigation_does_not_rescan_git_status() {
use std::process::Command;
let dir = std::env::temp_dir().join("konoma_speed_status_cache");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("sub")).unwrap();
for i in 0..4000 {
std::fs::write(dir.join(format!("f{i:04}.txt")), b"x\n").unwrap();
}
let repo = git2::Repository::init(&dir).unwrap();
{
let mut c = repo.config().unwrap();
c.set_str("user.name", "T").unwrap();
c.set_str("user.email", "t@t").unwrap();
c.set_str("commit.gpgsign", "false").ok();
}
let git = |a: &[&str]| {
Command::new("git")
.current_dir(&dir)
.args(a)
.output()
.unwrap();
};
git(&["add", "-A"]);
git(&["commit", "-qm", "init"]);
for i in 0..200 {
std::fs::write(dir.join(format!("f{i:04}.txt")), b"y\n").unwrap(); }
let root = dir.canonicalize().unwrap();
let mut app = crate::app::App::new(root.clone(), Config::default()).unwrap();
app.refresh_git_if_needed(); assert!(
app.git_has_changes(),
"変更が status に反映(スキャンが走った)"
);
crate::git::STATUS_CALLS.store(0, std::sync::atomic::Ordering::SeqCst);
let t = Instant::now();
for i in 0..40 {
app.root = if i % 2 == 0 {
root.join("sub")
} else {
root.clone()
};
app.refresh_git_if_needed();
}
let dt = t.elapsed();
assert_eq!(
crate::git::STATUS_CALLS.load(std::sync::atomic::Ordering::SeqCst),
0,
"同一 repo の 40 往復で git status を再スキャンした(workdir キャッシュ喪失)"
);
assert!(
dt < Duration::from_secs(1),
"同一 repo 往復が遅すぎる: {dt:?}"
);
std::fs::remove_dir_all(&dir).ok();
}