use crate::relative_test_name;
use crate::{ReplayOutput, TraceTool, replay_command, snapshot::SnapshotResult};
use anyhow::{Context, Result};
use image::{DynamicImage, Rgba};
use std::io::{Read, Write};
use std::path::Path;
use std::time::Duration;
use std::{path::PathBuf, time::Instant};
pub struct MockTrace {
file: PathBuf,
name: String,
}
impl MockTrace {
pub fn new(root: &Path, file: &Path) -> MockTrace {
MockTrace {
file: file.to_owned(),
name: relative_test_name(root, file),
}
}
}
impl TraceTool for MockTrace {
fn replay(&self, wrapper: Option<&str>, envs: &[(String, String)]) -> Result<ReplayOutput> {
Ok(self.run_replay_command(replay_command(
&["echo", "Measured FPS: 10.0 fps,"],
wrapper,
envs,
)))
}
fn fps(&self, output: &ReplayOutput) -> Result<f64> {
for line in output.stdout.lines() {
if let Some(rest) = line.strip_prefix("Measured FPS: ") {
if let Some(fps_str) = rest.split_whitespace().next() {
return fps_str.parse::<f64>().context("parsing mock FPS value");
}
}
}
anyhow::bail!("No 'Measured FPS:' line found in mock trace output")
}
fn name(&self) -> &str {
&self.name
}
fn can_snapshot(&self) -> bool {
true
}
fn snapshot(&self, output_dir: &str, loops: u32, timeout: Duration) -> Result<SnapshotResult> {
let output_dir_path = self.output_dir(output_dir)?.unwrap();
if std::env::var("MOCK_TIMEOUT").is_ok() {
let start = Instant::now();
let mut cmd = std::process::Command::new("sleep");
cmd.arg("60");
let output = self.run_replay_command_with_timeout(cmd, None, timeout);
return Ok(SnapshotResult {
files: vec![],
output,
runtime: start.elapsed(),
});
}
let break_rendering = std::env::var("MOCK_BREAK_RENDERING").is_ok();
let flake_rendering = std::env::var("MOCK_FLAKE_RENDERING").is_ok();
let modified = if break_rendering || flake_rendering {
let mut raw = Vec::new();
std::fs::File::open(&self.file)
.with_context(|| format!("opening {:?}", &self.file))?
.read_to_end(&mut raw)
.with_context(|| format!("reading {:?}", &self.file))?;
let image = image::load_from_memory(&raw)
.with_context(|| format!("loading mock trace file {:?} as an image", &self.file))?;
let modified = modify_image(&image);
let mut out: Vec<u8> = Vec::new();
modified
.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png)
.context("compressing modified png")?;
Some(out)
} else {
None
};
let start = Instant::now();
let mut files = Vec::new();
for frame in 1..=loops as usize {
let should_modify = break_rendering || (flake_rendering && frame == 3);
let filename = format!("snapshot{frame:04}.png");
let path = output_dir_path.join(&filename);
if should_modify {
std::fs::File::create(path)
.context("creating {path:?}")?
.write_all(modified.as_ref().unwrap())
.context("writing modified png to {path:?}")?;
} else {
std::fs::copy(&self.file, &path)
.with_context(|| format!("copying {:?} to {path:?}", &self.file))?;
};
files.push(PathBuf::from(&filename));
}
Ok(SnapshotResult {
files,
output: ReplayOutput {
exit_code: 0,
cmdline: format!("mock-trace-replay {:?}", self.file),
stdout: "mock-trace-replay stdout log contents".to_string(),
stderr: "mock-trace-replay stderr log contents".to_string(),
peak: crate::process_watcher::ProcessPeakMem::default(),
},
runtime: start.elapsed(),
})
}
}
fn modify_image(image: &DynamicImage) -> DynamicImage {
let mut rgba = image.to_rgba8();
for pixel in rgba.pixels_mut() {
if pixel.0[0..3] == [0u8; 3] {
*pixel = Rgba([0xd0u8, 0xd0, 0xd0, 0xd0]);
}
}
DynamicImage::ImageRgba8(rgba)
}