Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/assertions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,26 @@ pub struct AssertionChecker {

impl AssertionChecker {
/// Create a new assertion checker
pub fn new(log_file: &Path, work_dir: &Path, log_output_dir: Option<&Path>) -> Self {
pub fn new(
log_file: &Path,
work_dir: &Path,
log_output_dir: Option<&Path>,
output_subdir: Option<&str>,
) -> Self {
let log_data = Self::load_log_file(log_file);
let log_output_dir = log_output_dir.map(|p| {
let dir = if let Some(sub) = output_subdir {
p.join(sub)
} else {
p.to_path_buf()
};
std::fs::create_dir_all(&dir).ok();
dir
});
Self {
log_data,
work_dir: work_dir.to_path_buf(),
log_output_dir: log_output_dir.map(|p| p.to_path_buf()),
log_output_dir,
}
}

Expand Down
47 changes: 43 additions & 4 deletions src/assertions/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod tests {
fn create_checker(log_file: &str) -> AssertionChecker {
let log_path = Path::new(log_file);
let work_dir = tempfile::tempdir().unwrap();
AssertionChecker::new(log_path, work_dir.path(), None)
AssertionChecker::new(log_path, work_dir.path(), None, None)
}

#[test]
Expand Down Expand Up @@ -198,7 +198,7 @@ mod tests {
let empty_log = work_dir.path().join("empty.log");
std::fs::write(&empty_log, "").unwrap();

let checker = AssertionChecker::new(&empty_log, work_dir.path(), None);
let checker = AssertionChecker::new(&empty_log, work_dir.path(), None, None);
let init = checker.init_message();
assert!(init.is_none(), "empty log should not have init message");
}
Expand Down Expand Up @@ -254,7 +254,7 @@ mod tests {
let nonexistent_log = work_dir.path().join("nonexistent.log");

// Should not panic, just return empty checker
let checker = AssertionChecker::new(&nonexistent_log, work_dir.path(), None);
let checker = AssertionChecker::new(&nonexistent_log, work_dir.path(), None, None);
assert_eq!(
checker.log_data.len(),
0,
Expand All @@ -271,7 +271,7 @@ mod tests {
let empty_log = work_dir.path().join("empty.log");
std::fs::write(&empty_log, "").unwrap();

let checker = AssertionChecker::new(&empty_log, work_dir.path(), None);
let checker = AssertionChecker::new(&empty_log, work_dir.path(), None, None);

let check = crate::models::CheckStep {
name: "contains_patent_data".to_string(),
Expand Down Expand Up @@ -308,4 +308,43 @@ mod tests {
"file-contains should fail when string is absent"
);
}

#[test]
fn test_copy_to_output_creates_subdirectory() {
let work_dir = tempfile::tempdir().unwrap();
let output_dir = tempfile::tempdir().unwrap();
let test_file = work_dir.path().join("output.txt");
std::fs::write(&test_file, "test content").unwrap();

let empty_log = work_dir.path().join("empty.log");
std::fs::write(&empty_log, "").unwrap();

let checker = AssertionChecker::new(
&empty_log,
work_dir.path(),
Some(output_dir.path()),
Some("skill_test_20260404_050943"),
);

let check = crate::models::CheckStep {
name: "copy_file".to_string(),
command: CheckData {
command: "workspace-file".to_string(),
path: Some("output.txt".to_string()),
copy_to_output: Some(true),
..Default::default()
},
deny: false,
};

let result = checker.evaluate_check(&check);
assert!(result.is_ok(), "workspace-file with copy should pass");

let copied = output_dir
.path()
.join("skill_test_20260404_050943/output.txt");
assert!(copied.exists(), "file should be copied to subdirectory");
let content = std::fs::read_to_string(&copied).unwrap();
assert_eq!(content, "test content", "copied content should match");
}
}
12 changes: 10 additions & 2 deletions src/runtime/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,16 @@ impl TestExecutor {
}

// Run assertions
let checker =
AssertionChecker::new(&log_path, workspace.path(), self.log_output_dir.as_deref());
let output_subdir = self.log_output_dir.as_ref().map(|_| {
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
format!("{}_{}_{timestamp}", desc.skill_name, desc.test_name)
});
let checker = AssertionChecker::new(
&log_path,
workspace.path(),
self.log_output_dir.as_deref(),
output_subdir.as_deref(),
);
let check_results: Vec<CheckResult> = desc
.test
.checks
Expand Down
Loading