Track input file hashes in tests
This commit is contained in:
parent
399a4522e6
commit
9cf68c0581
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
2
test/commands/chart/best/pick_correct_score/in_0.toml
Normal file
2
test/commands/chart/best/pick_correct_score/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/antithese_74_kerning.jpg"
|
||||||
|
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"
|
2
test/commands/chart/best/pick_correct_score/in_1.toml
Normal file
2
test/commands/chart/best/pick_correct_score/in_1.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/fracture_ray_ex.jpg"
|
||||||
|
hash = "sha256_6aa2242fd40f71851ce826340fac5b53dbf3829568cf86bf5ed0ecc05ab89d2b"
|
2
test/commands/chart/best/pick_correct_score/in_2.toml
Normal file
2
test/commands/chart/best/pick_correct_score/in_2.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/fracture_ray_missed_ex.jpg"
|
||||||
|
hash = "sha256_6d6c10e9f946feb37df5925da0a14c70a9f42b68bb24a562bc295d301c956103"
|
2
test/commands/score/delete/delete_multiple/in_0.toml
Normal file
2
test/commands/score/delete/delete_multiple/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/antithese_74_kerning.jpg"
|
||||||
|
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"
|
2
test/commands/score/delete/delete_multiple/in_1.toml
Normal file
2
test/commands/score/delete/delete_multiple/in_1.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/alter_ego.jpg"
|
||||||
|
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"
|
2
test/commands/score/delete/delete_multiple/in_2.toml
Normal file
2
test/commands/score/delete/delete_multiple/in_2.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/genocider_24_kerning.jpg"
|
||||||
|
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"
|
2
test/commands/score/delete/delete_twice/in_0.toml
Normal file
2
test/commands/score/delete/delete_twice/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/alter_ego.jpg"
|
||||||
|
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"
|
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/alter_ego.jpg"
|
||||||
|
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"
|
2
test/commands/score/magic/single_pic/in_0.toml
Normal file
2
test/commands/score/magic/single_pic/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/alter_ego.jpg"
|
||||||
|
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"
|
2
test/commands/score/magic/weird_kerning/in_0.toml
Normal file
2
test/commands/score/magic/weird_kerning/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/antithese_74_kerning.jpg"
|
||||||
|
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"
|
2
test/commands/score/magic/weird_kerning/in_1.toml
Normal file
2
test/commands/score/magic/weird_kerning/in_1.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/genocider_24_kerning.jpg"
|
||||||
|
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"
|
2
test/commands/score/show/agrees_with_magic/in_0.toml
Normal file
2
test/commands/score/show/agrees_with_magic/in_0.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/antithese_74_kerning.jpg"
|
||||||
|
hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586"
|
2
test/commands/score/show/agrees_with_magic/in_1.toml
Normal file
2
test/commands/score/show/agrees_with_magic/in_1.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/alter_ego.jpg"
|
||||||
|
hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812"
|
2
test/commands/score/show/agrees_with_magic/in_2.toml
Normal file
2
test/commands/score/show/agrees_with_magic/in_2.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
filename = "test/screenshots/genocider_24_kerning.jpg"
|
||||||
|
hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511"
|
Loading…
Reference in a new issue