1
Fork 0

Track input file hashes in tests

This commit is contained in:
prescientmoon 2024-09-23 21:54:37 +02:00
parent 399a4522e6
commit 9cf68c0581
Signed by: prescientmoon
SSH key fingerprint: SHA256:WFp/cO76nbarETAoQcQXuV+0h7XJsEsOCI0UsyPIy6U
42 changed files with 118 additions and 31 deletions

View file

@ -77,7 +77,10 @@ impl MessageContext for CliContext {
NonZeroU64::new(666).unwrap() NonZeroU64::new(666).unwrap()
} }
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error> { async fn download(
_mutex: &tokio::sync::Mutex<&mut Self>,
attachment: &Self::Attachment,
) -> Result<Vec<u8>, Error> {
let res = tokio::fs::read(attachment).await?; let res = tokio::fs::read(attachment).await?;
Ok(res) Ok(res)
} }

View file

@ -5,6 +5,7 @@ use std::str::FromStr;
use poise::serenity_prelude::futures::future::join_all; use poise::serenity_prelude::futures::future::join_all;
use poise::serenity_prelude::{CreateAttachment, CreateEmbed}; use poise::serenity_prelude::{CreateAttachment, CreateEmbed};
use poise::CreateReply; use poise::CreateReply;
use tokio::sync::Mutex;
use crate::arcaea::play::Play; use crate::arcaea::play::Play;
use crate::context::{Error, ErrorKind, TaggedError, UserContext}; use crate::context::{Error, ErrorKind, TaggedError, UserContext};
@ -33,18 +34,24 @@ pub trait MessageContext {
fn filename(attachment: &Self::Attachment) -> &str; fn filename(attachment: &Self::Attachment) -> &str;
fn attachment_id(attachment: &Self::Attachment) -> NonZeroU64; fn attachment_id(attachment: &Self::Attachment) -> NonZeroU64;
/// Downloads a single file /// Downloads a single file.
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error>; ///
/// This takes a mutex, as downloads are often done in parallel.
async fn download(
mutex: &tokio::sync::Mutex<&mut Self>,
attachment: &Self::Attachment,
) -> Result<Vec<u8>, Error>;
/// Downloads every image /// Downloads every image
async fn download_images<'a>( async fn download_images<'a>(
&self, &mut self,
attachments: &'a [Self::Attachment], attachments: &'a [Self::Attachment],
) -> Result<Vec<(&'a Self::Attachment, Vec<u8>)>, Error> { ) -> Result<Vec<(&'a Self::Attachment, Vec<u8>)>, Error> {
let mutex = &Mutex::new(self);
let download_tasks = attachments let download_tasks = attachments
.iter() .iter()
.filter(|file| Self::is_image(file)) .filter(|file| Self::is_image(file))
.map(|file| async move { (file, self.download(file).await) }); .map(|file| async move { (file, Self::download(mutex, file).await) });
let downloaded = timed!("dowload_files", { join_all(download_tasks).await }); let downloaded = timed!("dowload_files", { join_all(download_tasks).await });
downloaded downloaded
@ -111,7 +118,10 @@ impl<'a> MessageContext for poise::Context<'a, UserContext, Error> {
attachment.dimensions().is_some() attachment.dimensions().is_some()
} }
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error> { async fn download(
_mutex: &tokio::sync::Mutex<&mut Self>,
attachment: &Self::Attachment,
) -> Result<Vec<u8>, Error> {
let res = poise::serenity_prelude::Attachment::download(attachment).await?; let res = poise::serenity_prelude::Attachment::download(attachment).await?;
Ok(res) Ok(res)
} }
@ -120,9 +130,12 @@ impl<'a> MessageContext for poise::Context<'a, UserContext, Error> {
// }}} // }}}
// {{{ Testing context // {{{ Testing context
pub mod mock { pub mod mock {
use std::{env, fs, path::PathBuf}; use std::{
env, fs,
path::{Path, PathBuf},
};
use anyhow::Context; use anyhow::{anyhow, Context};
use poise::serenity_prelude::CreateEmbed; use poise::serenity_prelude::CreateEmbed;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -139,6 +152,23 @@ pub mod mock {
hash: String, hash: String,
} }
impl AttachmentEssence {
pub fn new(filename: String, description: Option<String>, data: &[u8]) -> Self {
Self {
filename,
description,
hash: {
let hash = Sha256::digest(data);
let string = base16ct::lower::encode_string(&hash);
// We allocate twice, but it's only for testing,
// so it should be fineeeeeeee
format!("sha256_{string}")
},
}
}
}
/// Holds test-relevant data about a reply. /// Holds test-relevant data about a reply.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct ReplyEssence { pub struct ReplyEssence {
@ -159,17 +189,12 @@ pub mod mock {
attachments: message attachments: message
.attachments .attachments
.into_iter() .into_iter()
.map(|attachment| AttachmentEssence { .map(|attachment| {
filename: attachment.filename, AttachmentEssence::new(
description: attachment.description, attachment.filename,
hash: { attachment.description,
let hash = Sha256::digest(&attachment.data); &attachment.data,
let string = base16ct::lower::encode_string(&hash); )
// We allocate twice, but it's only at the end of tests,
// so it should be fineeeeeeee
format!("sha256_{string}")
},
}) })
.collect(), .collect(),
} }
@ -179,11 +204,16 @@ pub mod mock {
// {{{ Mock context // {{{ Mock context
/// A mock context usable for testing. Messages and attachments are /// A mock context usable for testing. Messages and attachments are
/// accumulated inside a vec, and can be used for golden testing /// accumulated inside a vec, and can be used for golden testing
/// (see [MockContext::golden]) /// (see [MockContext::golden]).
///
/// Moreover, downloaded attachment hashes are tracked so changes
/// to the input files require the test data to be regenerated.
pub struct MockContext { pub struct MockContext {
pub user_id: u64, pub user_id: u64,
pub data: UserContext, pub data: UserContext,
messages: Vec<ReplyEssence>, messages: Vec<ReplyEssence>,
downloaded_files: Vec<AttachmentEssence>,
} }
impl MockContext { impl MockContext {
@ -192,6 +222,7 @@ pub mod mock {
data, data,
user_id: 666, user_id: 666,
messages: vec![], messages: vec![],
downloaded_files: vec![],
} }
} }
@ -210,17 +241,27 @@ pub mod mock {
} }
fs::create_dir_all(path)?; fs::create_dir_all(path)?;
for (i, message) in self.messages.iter().enumerate() {
let message_file = path.join(format!("{i}.toml"));
if message_file.exists() { for (i, attachment) in self.downloaded_files.iter().enumerate() {
assert_eq!( let file = path.join(format!("in_{i}.toml"));
toml::to_string_pretty(message)?, Self::golden_impl(&file, &attachment)?;
fs::read_to_string(message_file)? }
);
} else { for (i, message) in self.messages.iter().enumerate() {
fs::write(&message_file, toml::to_string_pretty(message)?)?; let file = path.join(format!("out_{i}.toml"));
} Self::golden_impl(&file, message)?;
}
Ok(())
}
/// Runs the golden testing logic for a single file.
/// See [Self::golden] for more details.
fn golden_impl(path: &Path, message: &impl Serialize) -> Result<(), Error> {
if path.exists() {
assert_eq!(toml::to_string_pretty(message)?, fs::read_to_string(path)?);
} else {
fs::write(path, toml::to_string_pretty(message)?)?;
} }
Ok(()) Ok(())
@ -274,10 +315,24 @@ pub mod mock {
NonZeroU64::new(666).unwrap() NonZeroU64::new(666).unwrap()
} }
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error> { async fn download(
mutex: &tokio::sync::Mutex<&mut Self>,
attachment: &Self::Attachment,
) -> Result<Vec<u8>, Error> {
let res = tokio::fs::read(attachment) let res = tokio::fs::read(attachment)
.await .await
.with_context(|| format!("Could not download attachment {attachment:?}"))?; .with_context(|| format!("Could not download attachment {attachment:?}"))?;
let mut guard = mutex.lock().await;
guard.downloaded_files.push(AttachmentEssence::new(
attachment
.to_str()
.ok_or_else(|| anyhow!("Download path contains invalid unicode"))?
.to_owned(),
None,
&res,
));
Ok(res) Ok(res)
} }
// }}} // }}}

View file

@ -2,6 +2,7 @@
#![allow(clippy::needless_range_loop)] #![allow(clippy::needless_range_loop)]
#![allow(clippy::redundant_closure)] #![allow(clippy::redundant_closure)]
#![feature(iter_map_windows)] #![feature(iter_map_windows)]
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(array_try_map)] #![feature(array_try_map)]
#![feature(async_closure)] #![feature(async_closure)]

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/antithese_74_kerning.jpg"
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/fracture_ray_ex.jpg"
hash = "sha256_6aa2242fd40f71851ce826340fac5b53dbf3829568cf86bf5ed0ecc05ab89d2b"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/fracture_ray_missed_ex.jpg"
hash = "sha256_6d6c10e9f946feb37df5925da0a14c70a9f42b68bb24a562bc295d301c956103"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/antithese_74_kerning.jpg"
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/alter_ego.jpg"
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/genocider_24_kerning.jpg"
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/alter_ego.jpg"
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/alter_ego.jpg"
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/alter_ego.jpg"
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/antithese_74_kerning.jpg"
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/genocider_24_kerning.jpg"
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/antithese_74_kerning.jpg"
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/alter_ego.jpg"
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"

View file

@ -0,0 +1,2 @@
filename = "test/screenshots/genocider_24_kerning.jpg"
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"