diff --git a/src/bin/cli/context.rs b/src/bin/cli/context.rs index 5b208c1..66f66cd 100644 --- a/src/bin/cli/context.rs +++ b/src/bin/cli/context.rs @@ -77,7 +77,10 @@ impl MessageContext for CliContext { NonZeroU64::new(666).unwrap() } - async fn download(&self, attachment: &Self::Attachment) -> Result, Error> { + async fn download( + _mutex: &tokio::sync::Mutex<&mut Self>, + attachment: &Self::Attachment, + ) -> Result, Error> { let res = tokio::fs::read(attachment).await?; Ok(res) } diff --git a/src/commands/discord.rs b/src/commands/discord.rs index 05336d3..505e7a7 100644 --- a/src/commands/discord.rs +++ b/src/commands/discord.rs @@ -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, 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, Error>; /// Downloads every image async fn download_images<'a>( - &self, + &mut self, attachments: &'a [Self::Attachment], ) -> Result)>, 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, Error> { + async fn download( + _mutex: &tokio::sync::Mutex<&mut Self>, + attachment: &Self::Attachment, + ) -> Result, 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, 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, + downloaded_files: Vec, } 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, Error> { + async fn download( + mutex: &tokio::sync::Mutex<&mut Self>, + attachment: &Self::Attachment, + ) -> Result, 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) } // }}} diff --git a/src/lib.rs b/src/lib.rs index f3abf87..bbc7a4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] diff --git a/test/commands/chart/best/no_scores/0.toml b/test/commands/chart/best/no_scores/out_0.toml similarity index 100% rename from test/commands/chart/best/no_scores/0.toml rename to test/commands/chart/best/no_scores/out_0.toml diff --git a/test/commands/chart/best/pick_correct_score/in_0.toml b/test/commands/chart/best/pick_correct_score/in_0.toml new file mode 100644 index 0000000..7d300c8 --- /dev/null +++ b/test/commands/chart/best/pick_correct_score/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/antithese_74_kerning.jpg" +hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586" diff --git a/test/commands/chart/best/pick_correct_score/in_1.toml b/test/commands/chart/best/pick_correct_score/in_1.toml new file mode 100644 index 0000000..e49f315 --- /dev/null +++ b/test/commands/chart/best/pick_correct_score/in_1.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/fracture_ray_ex.jpg" +hash = "sha256_6aa2242fd40f71851ce826340fac5b53dbf3829568cf86bf5ed0ecc05ab89d2b" diff --git a/test/commands/chart/best/pick_correct_score/in_2.toml b/test/commands/chart/best/pick_correct_score/in_2.toml new file mode 100644 index 0000000..ff20a3f --- /dev/null +++ b/test/commands/chart/best/pick_correct_score/in_2.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/fracture_ray_missed_ex.jpg" +hash = "sha256_6d6c10e9f946feb37df5925da0a14c70a9f42b68bb24a562bc295d301c956103" diff --git a/test/commands/chart/best/pick_correct_score/0.toml b/test/commands/chart/best/pick_correct_score/out_0.toml similarity index 100% rename from test/commands/chart/best/pick_correct_score/0.toml rename to test/commands/chart/best/pick_correct_score/out_0.toml diff --git a/test/commands/chart/best/pick_correct_score/1.toml b/test/commands/chart/best/pick_correct_score/out_1.toml similarity index 100% rename from test/commands/chart/best/pick_correct_score/1.toml rename to test/commands/chart/best/pick_correct_score/out_1.toml diff --git a/test/commands/chart/info/last_byd/0.toml b/test/commands/chart/info/last_byd/out_0.toml similarity index 100% rename from test/commands/chart/info/last_byd/0.toml rename to test/commands/chart/info/last_byd/out_0.toml diff --git a/test/commands/chart/info/last_byd/1.toml b/test/commands/chart/info/last_byd/out_1.toml similarity index 100% rename from test/commands/chart/info/last_byd/1.toml rename to test/commands/chart/info/last_byd/out_1.toml diff --git a/test/commands/chart/info/no_suffix/0.toml b/test/commands/chart/info/no_suffix/out_0.toml similarity index 100% rename from test/commands/chart/info/no_suffix/0.toml rename to test/commands/chart/info/no_suffix/out_0.toml diff --git a/test/commands/chart/info/specify_difficulty/0.toml b/test/commands/chart/info/specify_difficulty/out_0.toml similarity index 100% rename from test/commands/chart/info/specify_difficulty/0.toml rename to test/commands/chart/info/specify_difficulty/out_0.toml diff --git a/test/commands/score/delete/delete_multiple/in_0.toml b/test/commands/score/delete/delete_multiple/in_0.toml new file mode 100644 index 0000000..7d300c8 --- /dev/null +++ b/test/commands/score/delete/delete_multiple/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/antithese_74_kerning.jpg" +hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586" diff --git a/test/commands/score/delete/delete_multiple/in_1.toml b/test/commands/score/delete/delete_multiple/in_1.toml new file mode 100644 index 0000000..b170ed2 --- /dev/null +++ b/test/commands/score/delete/delete_multiple/in_1.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/alter_ego.jpg" +hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812" diff --git a/test/commands/score/delete/delete_multiple/in_2.toml b/test/commands/score/delete/delete_multiple/in_2.toml new file mode 100644 index 0000000..73d1e95 --- /dev/null +++ b/test/commands/score/delete/delete_multiple/in_2.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/genocider_24_kerning.jpg" +hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511" diff --git a/test/commands/score/delete/delete_multiple/0.toml b/test/commands/score/delete/delete_multiple/out_0.toml similarity index 100% rename from test/commands/score/delete/delete_multiple/0.toml rename to test/commands/score/delete/delete_multiple/out_0.toml diff --git a/test/commands/score/delete/delete_multiple/1.toml b/test/commands/score/delete/delete_multiple/out_1.toml similarity index 100% rename from test/commands/score/delete/delete_multiple/1.toml rename to test/commands/score/delete/delete_multiple/out_1.toml diff --git a/test/commands/score/delete/delete_multiple/2.toml b/test/commands/score/delete/delete_multiple/out_2.toml similarity index 100% rename from test/commands/score/delete/delete_multiple/2.toml rename to test/commands/score/delete/delete_multiple/out_2.toml diff --git a/test/commands/score/delete/delete_twice/in_0.toml b/test/commands/score/delete/delete_twice/in_0.toml new file mode 100644 index 0000000..b170ed2 --- /dev/null +++ b/test/commands/score/delete/delete_twice/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/alter_ego.jpg" +hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812" diff --git a/test/commands/score/delete/delete_twice/0.toml b/test/commands/score/delete/delete_twice/out_0.toml similarity index 100% rename from test/commands/score/delete/delete_twice/0.toml rename to test/commands/score/delete/delete_twice/out_0.toml diff --git a/test/commands/score/delete/delete_twice/1.toml b/test/commands/score/delete/delete_twice/out_1.toml similarity index 100% rename from test/commands/score/delete/delete_twice/1.toml rename to test/commands/score/delete/delete_twice/out_1.toml diff --git a/test/commands/score/delete/delete_twice/2.toml b/test/commands/score/delete/delete_twice/out_2.toml similarity index 100% rename from test/commands/score/delete/delete_twice/2.toml rename to test/commands/score/delete/delete_twice/out_2.toml diff --git a/test/commands/score/delete/no_ids/0.toml b/test/commands/score/delete/no_ids/out_0.toml similarity index 100% rename from test/commands/score/delete/no_ids/0.toml rename to test/commands/score/delete/no_ids/out_0.toml diff --git a/test/commands/score/delete/no_show_after_delete/in_0.toml b/test/commands/score/delete/no_show_after_delete/in_0.toml new file mode 100644 index 0000000..b170ed2 --- /dev/null +++ b/test/commands/score/delete/no_show_after_delete/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/alter_ego.jpg" +hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812" diff --git a/test/commands/score/delete/no_show_after_delete/0.toml b/test/commands/score/delete/no_show_after_delete/out_0.toml similarity index 100% rename from test/commands/score/delete/no_show_after_delete/0.toml rename to test/commands/score/delete/no_show_after_delete/out_0.toml diff --git a/test/commands/score/delete/no_show_after_delete/1.toml b/test/commands/score/delete/no_show_after_delete/out_1.toml similarity index 100% rename from test/commands/score/delete/no_show_after_delete/1.toml rename to test/commands/score/delete/no_show_after_delete/out_1.toml diff --git a/test/commands/score/delete/no_show_after_delete/2.toml b/test/commands/score/delete/no_show_after_delete/out_2.toml similarity index 100% rename from test/commands/score/delete/no_show_after_delete/2.toml rename to test/commands/score/delete/no_show_after_delete/out_2.toml diff --git a/test/commands/score/delete/nonexistent_id/0.toml b/test/commands/score/delete/nonexistent_id/out_0.toml similarity index 100% rename from test/commands/score/delete/nonexistent_id/0.toml rename to test/commands/score/delete/nonexistent_id/out_0.toml diff --git a/test/commands/score/magic/no_pics/0.toml b/test/commands/score/magic/no_pics/out_0.toml similarity index 100% rename from test/commands/score/magic/no_pics/0.toml rename to test/commands/score/magic/no_pics/out_0.toml diff --git a/test/commands/score/magic/single_pic/in_0.toml b/test/commands/score/magic/single_pic/in_0.toml new file mode 100644 index 0000000..b170ed2 --- /dev/null +++ b/test/commands/score/magic/single_pic/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/alter_ego.jpg" +hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812" diff --git a/test/commands/score/magic/single_pic/0.toml b/test/commands/score/magic/single_pic/out_0.toml similarity index 100% rename from test/commands/score/magic/single_pic/0.toml rename to test/commands/score/magic/single_pic/out_0.toml diff --git a/test/commands/score/magic/weird_kerning/in_0.toml b/test/commands/score/magic/weird_kerning/in_0.toml new file mode 100644 index 0000000..7d300c8 --- /dev/null +++ b/test/commands/score/magic/weird_kerning/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/antithese_74_kerning.jpg" +hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586" diff --git a/test/commands/score/magic/weird_kerning/in_1.toml b/test/commands/score/magic/weird_kerning/in_1.toml new file mode 100644 index 0000000..73d1e95 --- /dev/null +++ b/test/commands/score/magic/weird_kerning/in_1.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/genocider_24_kerning.jpg" +hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511" diff --git a/test/commands/score/magic/weird_kerning/0.toml b/test/commands/score/magic/weird_kerning/out_0.toml similarity index 100% rename from test/commands/score/magic/weird_kerning/0.toml rename to test/commands/score/magic/weird_kerning/out_0.toml diff --git a/test/commands/score/show/agrees_with_magic/in_0.toml b/test/commands/score/show/agrees_with_magic/in_0.toml new file mode 100644 index 0000000..7d300c8 --- /dev/null +++ b/test/commands/score/show/agrees_with_magic/in_0.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/antithese_74_kerning.jpg" +hash = "sha256_7558ffb7c59018215955c4317ab14cb80f1d3bdf132d388c14a030ca526e2586" diff --git a/test/commands/score/show/agrees_with_magic/in_1.toml b/test/commands/score/show/agrees_with_magic/in_1.toml new file mode 100644 index 0000000..b170ed2 --- /dev/null +++ b/test/commands/score/show/agrees_with_magic/in_1.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/alter_ego.jpg" +hash = "sha256_5b88f5fc95b3146a0b2ed5c50e87410e4b26bb97026c293978790bf1344e0812" diff --git a/test/commands/score/show/agrees_with_magic/in_2.toml b/test/commands/score/show/agrees_with_magic/in_2.toml new file mode 100644 index 0000000..73d1e95 --- /dev/null +++ b/test/commands/score/show/agrees_with_magic/in_2.toml @@ -0,0 +1,2 @@ +filename = "test/screenshots/genocider_24_kerning.jpg" +hash = "sha256_45556caf74d37b0aa818faed3d7efe4baf870e25005b80963dbe60b89638f511" diff --git a/test/commands/score/show/agrees_with_magic/0.toml b/test/commands/score/show/agrees_with_magic/out_0.toml similarity index 100% rename from test/commands/score/show/agrees_with_magic/0.toml rename to test/commands/score/show/agrees_with_magic/out_0.toml diff --git a/test/commands/score/show/agrees_with_magic/1.toml b/test/commands/score/show/agrees_with_magic/out_1.toml similarity index 100% rename from test/commands/score/show/agrees_with_magic/1.toml rename to test/commands/score/show/agrees_with_magic/out_1.toml diff --git a/test/commands/score/show/no_ids/0.toml b/test/commands/score/show/no_ids/out_0.toml similarity index 100% rename from test/commands/score/show/no_ids/0.toml rename to test/commands/score/show/no_ids/out_0.toml diff --git a/test/commands/score/show/nonexistent_id/0.toml b/test/commands/score/show/nonexistent_id/out_0.toml similarity index 100% rename from test/commands/score/show/nonexistent_id/0.toml rename to test/commands/score/show/nonexistent_id/out_0.toml