Skip to content

Commit 05ef2ae

Browse files
authored
add version check in job script (#206)
1 parent 2ebdaad commit 05ef2ae

2 files changed

Lines changed: 70 additions & 8 deletions

File tree

src/checksum.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{Context, Result, bail};
1+
use anyhow::{bail, Context, Result};
22
use md5::{Digest, Md5};
33
use serde::{Deserialize, Serialize};
44
use std::collections::HashMap;
@@ -175,10 +175,7 @@ pub fn validate_checksums(
175175
///
176176
/// * `Result<()>` - Ok if the live version matches the stored one, Err otherwise
177177
pub fn validate_haddock3_version_live(checksum_file: &Path) -> Result<()> {
178-
let stored_content =
179-
fs::read_to_string(checksum_file).context("Failed to read checksum file")?;
180-
let stored_data: ChecksumData =
181-
serde_json::from_str(&stored_content).context("Failed to parse checksum file")?;
178+
let stored_data = read_checksum_data(checksum_file)?;
182179

183180
let live_version = crate::utils::get_haddock3_version()?;
184181

@@ -193,6 +190,20 @@ pub fn validate_haddock3_version_live(checksum_file: &Path) -> Result<()> {
193190
Ok(())
194191
}
195192

193+
fn read_checksum_data(checksum_file: &Path) -> Result<ChecksumData> {
194+
let stored_content =
195+
fs::read_to_string(checksum_file).context("Failed to read checksum file")?;
196+
serde_json::from_str(&stored_content).context("Failed to parse checksum file")
197+
}
198+
199+
/// Returns the haddock3 version recorded in `checksum.json` when the benchmark started.
200+
///
201+
/// Used to embed the expected version into a generated SLURM job script so it can be
202+
/// re-checked from inside the job itself, after `slurm_prologue` has run.
203+
pub fn expected_haddock3_version(checksum_file: &Path) -> Result<String> {
204+
Ok(read_checksum_data(checksum_file)?.haddock3_version)
205+
}
206+
196207
fn find_modified(
197208
current_checksums: HashMap<String, String>,
198209
stored_checksums: HashMap<String, String>,
@@ -265,7 +276,7 @@ mod tests {
265276
use super::*;
266277
use std::collections::HashMap;
267278
use std::io::Write;
268-
use tempfile::{NamedTempFile, tempdir};
279+
use tempfile::{tempdir, NamedTempFile};
269280

270281
#[test]
271282
fn test_calculate_checksum() {
@@ -433,7 +444,7 @@ mod tests {
433444
let mut stored = HashMap::new();
434445
stored.insert("file1.txt".to_string(), "checksum1_changed".to_string()); // Changed
435446
stored.insert("file4.txt".to_string(), "checksum4".to_string()); // Removed
436-
// file2.txt and file3.txt are new
447+
// file2.txt and file3.txt are new
437448

438449
// Find modified files
439450
let error_msg = find_modified(current, stored);
@@ -570,4 +581,19 @@ mod tests {
570581
let result = validate_haddock3_version_live(&checksum_file);
571582
assert!(result.is_err());
572583
}
584+
585+
#[test]
586+
fn test_expected_haddock3_version() {
587+
let temp_dir = tempdir().unwrap();
588+
let checksum_file = temp_dir.path().join("checksum.json");
589+
590+
let data = ChecksumData {
591+
files: HashMap::new(),
592+
haddock3_version: "2026.3.0".to_string(),
593+
};
594+
fs::write(&checksum_file, serde_json::to_string_pretty(&data).unwrap()).unwrap();
595+
596+
let version = expected_haddock3_version(&checksum_file).unwrap();
597+
assert_eq!(version, "2026.3.0");
598+
}
573599
}

src/job.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,22 +405,34 @@ impl Job {
405405
let mut file = fs::File::create(&job_script)?;
406406

407407
let absolute_wd = canonicalize(&self.wd).unwrap_or_else(|_| self.wd.to_path_buf());
408+
let haddock3_exe = find_haddock3_executable()?;
408409

409410
// Write SLURM header
410411
let header = self.generate_slurm_header();
411412

412413
let prologue = self.format_slurm_prologue();
413414

415+
// slurm_prologue can alter the environment (module load/conda activate/etc.)
416+
// before haddock3 runs; re-check the version from inside the job itself so
417+
// a swap isn't invisible to the login-node-only checks in checksum.rs.
418+
let mut version_check = String::new();
419+
if self.general.slurm_prologue.is_some() {
420+
let checksum_file = self.general.work_dir.join("checksum.json");
421+
let expected_version = crate::checksum::expected_haddock3_version(&checksum_file)?;
422+
version_check = format_haddock3_version_check(&haddock3_exe, &expected_version);
423+
}
424+
414425
// Write job body
415426
let body = format!(
416427
"cd {}\n{} {}\n",
417428
absolute_wd.display(),
418-
find_haddock3_executable()?,
429+
haddock3_exe,
419430
WORKFLOW_FILENAME
420431
);
421432

422433
file.write_all(header.as_bytes())?;
423434
file.write_all(prologue.as_bytes())?;
435+
file.write_all(version_check.as_bytes())?;
424436
file.write_all(body.as_bytes())?;
425437

426438
Ok(())
@@ -540,6 +552,21 @@ impl Job {
540552
}
541553
}
542554

555+
/// Renders a shell snippet that re-checks `haddock3_exe --version` against `expected_version`,
556+
/// exiting the job script with an error on mismatch. Meant to run after `slurm_prologue`
557+
/// commands, in the same shell state, since those can alter the environment haddock3 sees.
558+
fn format_haddock3_version_check(haddock3_exe: &str, expected_version: &str) -> String {
559+
format!(
560+
r#"HADDOCK3_VERSION_OUTPUT="$({haddock3_exe} --version 2>&1)"
561+
ACTUAL_HADDOCK3_VERSION="${{HADDOCK3_VERSION_OUTPUT##* - }}"
562+
if [ "$ACTUAL_HADDOCK3_VERSION" != "{expected_version}" ]; then
563+
echo "ERROR: haddock3 version mismatch after slurm_prologue: expected {expected_version}, found $ACTUAL_HADDOCK3_VERSION. Check slurm_prologue for environment changes (module load/conda activate/etc.)." >&2
564+
exit 1
565+
fi
566+
"#
567+
)
568+
}
569+
543570
#[cfg(test)]
544571
mod tests {
545572
use super::*;
@@ -1073,6 +1100,15 @@ mod tests {
10731100
assert_eq!(job.format_slurm_prologue(), "module load foo\n");
10741101
}
10751102

1103+
#[test]
1104+
fn test_format_haddock3_version_check_contents() {
1105+
let check = format_haddock3_version_check("/opt/haddock3/bin/haddock3", "2026.3.0");
1106+
assert!(check.contains("/opt/haddock3/bin/haddock3 --version"));
1107+
assert!(check.contains("2026.3.0"));
1108+
assert!(check.contains("ACTUAL_HADDOCK3_VERSION"));
1109+
assert!(check.contains("exit 1"));
1110+
}
1111+
10761112
fn make_job_with_modules(modules: IndexMap<String, Value>) -> Job {
10771113
Job {
10781114
name: "test".to_string(),

0 commit comments

Comments
 (0)