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()
}
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?;
Ok(res)
}

View file

@ -5,6 +5,7 @@ use std::str::FromStr;
use poise::serenity_prelude::futures::future::join_all;
use poise::serenity_prelude::{CreateAttachment, CreateEmbed};
use poise::CreateReply;
use tokio::sync::Mutex;
use crate::arcaea::play::Play;
use crate::context::{Error, ErrorKind, TaggedError, UserContext};
@ -33,18 +34,24 @@ pub trait MessageContext {
fn filename(attachment: &Self::Attachment) -> &str;
fn attachment_id(attachment: &Self::Attachment) -> NonZeroU64;
/// Downloads a single file
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error>;
/// Downloads a single file.
///
/// 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
async fn download_images<'a>(
&self,
&mut self,
attachments: &'a [Self::Attachment],
) -> Result<Vec<(&'a Self::Attachment, Vec<u8>)>, Error> {
let mutex = &Mutex::new(self);
let download_tasks = attachments
.iter()
.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 });
downloaded
@ -111,7 +118,10 @@ impl<'a> MessageContext for poise::Context<'a, UserContext, Error> {
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?;
Ok(res)
}
@ -120,9 +130,12 @@ impl<'a> MessageContext for poise::Context<'a, UserContext, Error> {
// }}}
// {{{ Testing context
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 serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@ -139,6 +152,23 @@ pub mod mock {
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.
#[derive(Debug, Clone, Serialize)]
pub struct ReplyEssence {
@ -159,17 +189,12 @@ pub mod mock {
attachments: message
.attachments
.into_iter()
.map(|attachment| AttachmentEssence {
filename: attachment.filename,
description: attachment.description,
hash: {
let hash = Sha256::digest(&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}")
},
.map(|attachment| {
AttachmentEssence::new(
attachment.filename,
attachment.description,
&attachment.data,
)
})
.collect(),
}
@ -179,11 +204,16 @@ pub mod mock {
// {{{ Mock context
/// A mock context usable for testing. Messages and attachments are
/// 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 user_id: u64,
pub data: UserContext,
messages: Vec<ReplyEssence>,
downloaded_files: Vec<AttachmentEssence>,
}
impl MockContext {
@ -192,6 +222,7 @@ pub mod mock {
data,
user_id: 666,
messages: vec![],
downloaded_files: vec![],
}
}
@ -210,17 +241,27 @@ pub mod mock {
}
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() {
assert_eq!(
toml::to_string_pretty(message)?,
fs::read_to_string(message_file)?
);
} else {
fs::write(&message_file, toml::to_string_pretty(message)?)?;
for (i, attachment) in self.downloaded_files.iter().enumerate() {
let file = path.join(format!("in_{i}.toml"));
Self::golden_impl(&file, &attachment)?;
}
for (i, message) in self.messages.iter().enumerate() {
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(())
@ -274,10 +315,24 @@ pub mod mock {
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
.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)
}
// }}}

View file

@ -2,6 +2,7 @@
#![allow(clippy::needless_range_loop)]
#![allow(clippy::redundant_closure)]
#![feature(iter_map_windows)]
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(let_chains)]
#![feature(array_try_map)]
#![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"