Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: 828bbfe071b2e8b2f117828f937cd7eb9c238ef0
Timestamp: Tue, 09 May 2023 13:36:23 +0000 (1 year ago)

+352 -107 +/-4 browse
grcov: increase coverage with rustdoc tests
1diff --git a/.github/workflows/grcov.yaml b/.github/workflows/grcov.yaml
2index 6646048..ffa1c09 100644
3--- a/.github/workflows/grcov.yaml
4+++ b/.github/workflows/grcov.yaml
5 @@ -38,8 +38,8 @@ jobs:
6 args: --all --all-features --no-fail-fast
7 env:
8 CARGO_INCREMENTAL: '0'
9- RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
10- RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
11+ RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests -Cinstrument-coverage'
12+ RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests -Cinstrument-coverage'
13 - uses: actions-rs/grcov@v0.1
14 with:
15 config: .github/grcov.yml
16 diff --git a/core/src/connection.rs b/core/src/connection.rs
17index 875d451..2c4580e 100644
18--- a/core/src/connection.rs
19+++ b/core/src/connection.rs
20 @@ -141,7 +141,7 @@ impl Connection {
21 ///
22 /// # Example
23 ///
24- /// ```rust
25+ /// ```rust,no_run
26 /// use mailpot::{Connection, Configuration};
27 /// use melib::smtp::{SmtpServerConf, SmtpAuth, SmtpSecurity};
28 /// #
29 @@ -807,3 +807,39 @@ pub mod transaction {
30 Panic,
31 }
32 }
33+
34+ #[cfg(test)]
35+ mod tests {
36+ use super::*;
37+
38+ #[test]
39+ fn test_new_connection() {
40+ use melib::smtp::{SmtpAuth, SmtpSecurity, SmtpServerConf};
41+ use tempfile::TempDir;
42+
43+ use crate::SendMail;
44+
45+ let tmp_dir = TempDir::new().unwrap();
46+ let db_path = tmp_dir.path().join("mpot.db");
47+ let data_path = tmp_dir.path().to_path_buf();
48+ let config = Configuration {
49+ send_mail: SendMail::Smtp(SmtpServerConf {
50+ hostname: "127.0.0.1".into(),
51+ port: 25,
52+ envelope_from: "foo-chat@example.com".into(),
53+ auth: SmtpAuth::None,
54+ security: SmtpSecurity::None,
55+ extensions: Default::default(),
56+ }),
57+ db_path,
58+ data_path,
59+ administrators: vec![],
60+ };
61+ assert_eq!(
62+ &Connection::open_db(config.clone()).unwrap_err().to_string(),
63+ "Database doesn't exist"
64+ );
65+
66+ _ = Connection::open_or_create_db(config).unwrap();
67+ }
68+ }
69 diff --git a/web/src/minijinja_utils.rs b/web/src/minijinja_utils.rs
70index 5a1bdd0..684ffb6 100644
71--- a/web/src/minijinja_utils.rs
72+++ b/web/src/minijinja_utils.rs
73 @@ -189,44 +189,6 @@ impl minijinja::value::StructObject for MailingList {
74
75 /// Return a vector of weeks, with each week being a vector of 7 days and
76 /// corresponding sum of posts per day.
77- ///
78- ///
79- /// # Example
80- ///
81- /// ```rust
82- /// # use mailpot_web::minijinja_utils::calendarize;
83- /// # use minijinja::Environment;
84- /// # use minijinja::value::Value;
85- /// # use std::collections::HashMap;
86- ///
87- /// let mut env = Environment::new();
88- /// env.add_function("calendarize", calendarize);
89- ///
90- /// let month = "2001-09";
91- /// let mut hist = [0usize; 31];
92- /// hist[15] = 5;
93- /// hist[1] = 1;
94- /// hist[0] = 512;
95- /// hist[30] = 30;
96- /// assert_eq!(
97- /// &env.render_str(
98- /// "{% set c=calendarize(month, hists) %}Month: {{ c.month }} Month Name: {{ \
99- /// c.month_name }} Month Int: {{ c.month_int }} Year: {{ c.year }} Sum: {{ c.sum }} {% \
100- /// for week in c.weeks %}{% for day in week %}{% set num = c.hist[day-1] %}({{ day }}, \
101- /// {{ num }}){% endfor %}{% endfor %}",
102- /// minijinja::context! {
103- /// month,
104- /// hists => vec![(month.to_string(), hist)].into_iter().collect::<HashMap<String, [usize;
105- /// 31]>>(),
106- /// }
107- /// )
108- /// .unwrap(),
109- /// "Month: 2001-09 Month Name: September Month Int: 9 Year: 2001 Sum: 548 (0, 30)(0, 30)(0, \
110- /// 30)(0, 30)(0, 30)(1, 512)(2, 1)(3, 0)(4, 0)(5, 0)(6, 0)(7, 0)(8, 0)(9, 0)(10, 0)(11, \
111- /// 0)(12, 0)(13, 0)(14, 0)(15, 0)(16, 5)(17, 0)(18, 0)(19, 0)(20, 0)(21, 0)(22, 0)(23, \
112- /// 0)(24, 0)(25, 0)(26, 0)(27, 0)(28, 0)(29, 0)(30, 0)"
113- /// );
114- /// ```
115 pub fn calendarize(
116 _state: &minijinja::State,
117 args: Value,
118 @@ -294,7 +256,7 @@ pub fn calendarize(
119 ///
120 /// # Examples
121 ///
122- /// ```rust
123+ /// ```rust,no_run
124 /// # use mailpot_web::pluralize;
125 /// # use minijinja::Environment;
126 ///
127 @@ -436,26 +398,6 @@ pub fn pluralize(
128 /// `strip_carets` filter for [`minijinja`].
129 ///
130 /// Removes `[<>]` from message ids.
131- ///
132- /// # Examples
133- ///
134- /// ```rust
135- /// # use mailpot_web::strip_carets;
136- /// # use minijinja::Environment;
137- ///
138- /// let mut env = Environment::new();
139- /// env.add_filter("strip_carets", strip_carets);
140- /// assert_eq!(
141- /// &env.render_str(
142- /// "{{ msg_id | strip_carets }}",
143- /// minijinja::context! {
144- /// msg_id => "<hello1@example.com>",
145- /// }
146- /// )
147- /// .unwrap(),
148- /// "hello1@example.com",
149- /// );
150- /// ```
151 pub fn strip_carets(_state: &minijinja::State, arg: Value) -> std::result::Result<Value, Error> {
152 Ok(Value::from(
153 arg.as_str()
154 @@ -475,7 +417,7 @@ pub fn strip_carets(_state: &minijinja::State, arg: Value) -> std::result::Resul
155 ///
156 /// # Examples
157 ///
158- /// ```rust
159+ /// ```rust,no_run
160 /// # use mailpot_web::urlize;
161 /// # use minijinja::Environment;
162 /// # use minijinja::value::Value;
163 @@ -505,7 +447,7 @@ pub fn urlize(state: &minijinja::State, arg: Value) -> std::result::Result<Value
164 /// Make an html heading: `h1, h2, h3` etc.
165 ///
166 /// # Example
167- /// ```
168+ /// ```rust,no_run
169 /// use mailpot_web::minijinja_utils::heading;
170 /// use minijinja::value::Value;
171 ///
172 @@ -601,3 +543,227 @@ pub fn heading(level: Value, text: Value, id: Option<Value>) -> std::result::Res
173 )))
174 }
175 }
176+
177+ #[cfg(test)]
178+ mod tests {
179+ use super::*;
180+
181+ #[test]
182+ fn test_pluralize() {
183+ let mut env = Environment::new();
184+ env.add_filter("pluralize", pluralize);
185+ for (num, s) in [
186+ (0, "You have 0 messages."),
187+ (1, "You have 1 message."),
188+ (10, "You have 10 messages."),
189+ ] {
190+ assert_eq!(
191+ &env.render_str(
192+ "You have {{ num_messages }} message{{ num_messages|pluralize }}.",
193+ minijinja::context! {
194+ num_messages => num,
195+ }
196+ )
197+ .unwrap(),
198+ s
199+ );
200+ }
201+
202+ for (num, s) in [
203+ (0, "You have 0 walruses."),
204+ (1, "You have 1 walrus."),
205+ (10, "You have 10 walruses."),
206+ ] {
207+ assert_eq!(
208+ &env.render_str(
209+ r#"You have {{ num_walruses }} walrus{{ num_walruses|pluralize(None, "es") }}."#,
210+ minijinja::context! {
211+ num_walruses => num,
212+ }
213+ )
214+ .unwrap(),
215+ s
216+ );
217+ }
218+
219+ for (num, s) in [
220+ (0, "You have 0 cherries."),
221+ (1, "You have 1 cherry."),
222+ (10, "You have 10 cherries."),
223+ ] {
224+ assert_eq!(
225+ &env.render_str(
226+ r#"You have {{ num_cherries }} cherr{{ num_cherries|pluralize("y", "ies") }}."#,
227+ minijinja::context! {
228+ num_cherries => num,
229+ }
230+ )
231+ .unwrap(),
232+ s
233+ );
234+ }
235+
236+ assert_eq!(
237+ &env.render_str(
238+ r#"You have {{ num_cherries|length }} cherr{{ num_cherries|pluralize("y", "ies") }}."#,
239+ minijinja::context! {
240+ num_cherries => vec![(); 5],
241+ }
242+ )
243+ .unwrap(),
244+ "You have 5 cherries."
245+ );
246+
247+ assert_eq!(
248+ &env.render_str(
249+ r#"You have {{ num_cherries }} cherr{{ num_cherries|pluralize("y", "ies") }}."#,
250+ minijinja::context! {
251+ num_cherries => "5",
252+ }
253+ )
254+ .unwrap(),
255+ "You have 5 cherries."
256+ );
257+ assert_eq!(
258+ &env.render_str(
259+ r#"You have 1 cherr{{ num_cherries|pluralize("y", "ies") }}."#,
260+ minijinja::context! {
261+ num_cherries => true,
262+ }
263+ )
264+ .unwrap(),
265+ "You have 1 cherry.",
266+ );
267+ assert_eq!(
268+ &env.render_str(
269+ r#"You have {{ num_cherries }} cherr{{ num_cherries|pluralize("y", "ies") }}."#,
270+ minijinja::context! {
271+ num_cherries => 0.5f32,
272+ }
273+ )
274+ .unwrap_err()
275+ .to_string(),
276+ "invalid operation: Pluralize argument is not an integer, or a sequence / object with \
277+ a length but of type number (in <string>:1)",
278+ );
279+ }
280+
281+ #[test]
282+ fn test_urlize() {
283+ let mut env = Environment::new();
284+ env.add_function("urlize", urlize);
285+ env.add_global(
286+ "root_url_prefix",
287+ Value::from_safe_string("/lists/prefix/".to_string()),
288+ );
289+ assert_eq!(
290+ &env.render_str(
291+ "<a href=\"{{ urlize(\"path/index.html\") }}\">link</a>",
292+ minijinja::context! {}
293+ )
294+ .unwrap(),
295+ "<a href=\"/lists/prefix/path/index.html\">link</a>",
296+ );
297+ }
298+
299+ #[test]
300+ fn test_heading() {
301+ assert_eq!(
302+ "<h1 id=\"bl-bfa-b-ah-b-asdb-hadas-d\">bl bfa B AH bAsdb hadas d<a \
303+ class=\"self-link\" href=\"#bl-bfa-b-ah-b-asdb-hadas-d\"></a></h1>",
304+ &heading(1.into(), "bl bfa B AH bAsdb hadas d".into(), None)
305+ .unwrap()
306+ .to_string()
307+ );
308+ assert_eq!(
309+ "<h2 id=\"short\">bl bfa B AH bAsdb hadas d<a class=\"self-link\" \
310+ href=\"#short\"></a></h2>",
311+ &heading(
312+ 2.into(),
313+ "bl bfa B AH bAsdb hadas d".into(),
314+ Some("short".into())
315+ )
316+ .unwrap()
317+ .to_string()
318+ );
319+ assert_eq!(
320+ r#"invalid operation: first heading() argument must be an unsigned integer less than 7 and positive"#,
321+ &heading(
322+ 0.into(),
323+ "bl bfa B AH bAsdb hadas d".into(),
324+ Some("short".into())
325+ )
326+ .unwrap_err()
327+ .to_string()
328+ );
329+ assert_eq!(
330+ r#"invalid operation: first heading() argument must be an unsigned integer less than 7 and positive"#,
331+ &heading(
332+ 8.into(),
333+ "bl bfa B AH bAsdb hadas d".into(),
334+ Some("short".into())
335+ )
336+ .unwrap_err()
337+ .to_string()
338+ );
339+ assert_eq!(
340+ r#"invalid operation: first heading() argument is not an integer < 7 but of type sequence"#,
341+ &heading(
342+ Value::from(vec![Value::from(1)]),
343+ "bl bfa B AH bAsdb hadas d".into(),
344+ Some("short".into())
345+ )
346+ .unwrap_err()
347+ .to_string()
348+ );
349+ }
350+
351+ #[test]
352+ fn test_strip_carets() {
353+ let mut env = Environment::new();
354+ env.add_filter("strip_carets", strip_carets);
355+ assert_eq!(
356+ &env.render_str(
357+ "{{ msg_id | strip_carets }}",
358+ minijinja::context! {
359+ msg_id => "<hello1@example.com>",
360+ }
361+ )
362+ .unwrap(),
363+ "hello1@example.com",
364+ );
365+ }
366+
367+ #[test]
368+ fn test_calendarize() {
369+ use std::collections::HashMap;
370+
371+ let mut env = Environment::new();
372+ env.add_function("calendarize", calendarize);
373+
374+ let month = "2001-09";
375+ let mut hist = [0usize; 31];
376+ hist[15] = 5;
377+ hist[1] = 1;
378+ hist[0] = 512;
379+ hist[30] = 30;
380+ assert_eq!(
381+ &env.render_str(
382+ "{% set c=calendarize(month, hists) %}Month: {{ c.month }} Month Name: {{ \
383+ c.month_name }} Month Int: {{ c.month_int }} Year: {{ c.year }} Sum: {{ c.sum }} {% \
384+ for week in c.weeks %}{% for day in week %}{% set num = c.hist[day-1] %}({{ day }}, \
385+ {{ num }}){% endfor %}{% endfor %}",
386+ minijinja::context! {
387+ month,
388+ hists => vec![(month.to_string(), hist)].into_iter().collect::<HashMap<String, [usize;
389+ 31]>>(),
390+ }
391+ )
392+ .unwrap(),
393+ "Month: 2001-09 Month Name: September Month Int: 9 Year: 2001 Sum: 548 (0, 30)(0, 30)(0, \
394+ 30)(0, 30)(0, 30)(1, 512)(2, 1)(3, 0)(4, 0)(5, 0)(6, 0)(7, 0)(8, 0)(9, 0)(10, 0)(11, \
395+ 0)(12, 0)(13, 0)(14, 0)(15, 0)(16, 5)(17, 0)(18, 0)(19, 0)(20, 0)(21, 0)(22, 0)(23, \
396+ 0)(24, 0)(25, 0)(26, 0)(27, 0)(28, 0)(29, 0)(30, 0)"
397+ );
398+ }
399+ }
400 diff --git a/web/src/utils.rs b/web/src/utils.rs
401index b99b866..4bfa0a4 100644
402--- a/web/src/utils.rs
403+++ b/web/src/utils.rs
404 @@ -66,7 +66,7 @@ impl Message {
405 ///
406 /// # Example
407 ///
408- /// ```rust
409+ /// ```no_run
410 /// # use mailpot_web::utils::{Message, Level, SessionMessages};
411 /// struct Session(Vec<Message>);
412 ///
413 @@ -135,17 +135,6 @@ impl SessionMessages for WritableSession {
414
415 /// Deserialize a string integer into `i64`, because POST parameters are
416 /// strings.
417- ///
418- /// ```
419- /// # use mailpot_web::utils::IntPOST;
420- /// # use mailpot::serde_json::{self, json};
421- /// assert_eq!(
422- /// IntPOST(5),
423- /// serde_json::from_str::<IntPOST>("\"5\"").unwrap()
424- /// );
425- /// assert_eq!(IntPOST(5), serde_json::from_str::<IntPOST>("5").unwrap());
426- /// assert_eq!(&json! { IntPOST(5) }.to_string(), "5");
427- /// ```
428 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)]
429 #[repr(transparent)]
430 pub struct IntPOST(pub i64);
431 @@ -201,20 +190,6 @@ impl<'de> serde::Deserialize<'de> for IntPOST {
432
433 /// Deserialize a string integer into `bool`, because POST parameters are
434 /// strings.
435- ///
436- /// ```
437- /// # use mailpot_web::utils::BoolPOST;
438- /// # use mailpot::serde_json::{self, json};
439- /// assert_eq!(
440- /// BoolPOST(true),
441- /// serde_json::from_str::<BoolPOST>("true").unwrap()
442- /// );
443- /// assert_eq!(
444- /// BoolPOST(true),
445- /// serde_json::from_str::<BoolPOST>("\"true\"").unwrap()
446- /// );
447- /// assert_eq!(&json! { BoolPOST(false) }.to_string(), "false");
448- /// ```
449 #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Hash)]
450 #[repr(transparent)]
451 pub struct BoolPOST(pub bool);
452 @@ -261,23 +236,6 @@ impl<'de> serde::Deserialize<'de> for BoolPOST {
453 }
454 }
455
456- /// ```
457- /// use axum::response::Redirect;
458- /// use mailpot_web::Next;
459- ///
460- /// let next = Next {
461- /// next: Some("foo".to_string()),
462- /// };
463- /// assert_eq!(
464- /// format!("{:?}", Redirect::to("foo")),
465- /// format!("{:?}", next.or_else(|| "bar".to_string()))
466- /// );
467- /// let next = Next { next: None };
468- /// assert_eq!(
469- /// format!("{:?}", Redirect::to("bar")),
470- /// format!("{:?}", next.or_else(|| "bar".to_string()))
471- /// );
472- /// ```
473 #[derive(Debug, Clone, serde::Deserialize)]
474 pub struct Next {
475 #[serde(default, deserialize_with = "empty_string_as_none")]
476 @@ -530,3 +488,88 @@ pub fn thread_roots(
477 ret.sort_by_key(|(_, _, key)| std::cmp::Reverse(*key));
478 ret
479 }
480+
481+ #[cfg(test)]
482+ mod tests {
483+ use super::*;
484+
485+ #[test]
486+ fn test_session() {
487+ struct Session(Vec<Message>);
488+
489+ impl SessionMessages for Session {
490+ type Error = std::convert::Infallible;
491+ fn drain_messages(&mut self) -> Vec<Message> {
492+ std::mem::take(&mut self.0)
493+ }
494+
495+ fn add_message(&mut self, m: Message) -> Result<(), std::convert::Infallible> {
496+ self.0.push(m);
497+ Ok(())
498+ }
499+ }
500+ let mut s = Session(vec![]);
501+ s.add_message(Message {
502+ message: "foo".into(),
503+ level: Level::default(),
504+ })
505+ .unwrap();
506+ s.add_message(Message {
507+ message: "bar".into(),
508+ level: Level::Error,
509+ })
510+ .unwrap();
511+ assert_eq!(
512+ s.drain_messages().as_slice(),
513+ [
514+ Message {
515+ message: "foo".into(),
516+ level: Level::default(),
517+ },
518+ Message {
519+ message: "bar".into(),
520+ level: Level::Error
521+ }
522+ ]
523+ .as_slice()
524+ );
525+ assert!(s.0.is_empty());
526+ }
527+
528+ #[test]
529+ fn test_post_serde() {
530+ use mailpot::serde_json::{self, json};
531+ assert_eq!(
532+ IntPOST(5),
533+ serde_json::from_str::<IntPOST>("\"5\"").unwrap()
534+ );
535+ assert_eq!(IntPOST(5), serde_json::from_str::<IntPOST>("5").unwrap());
536+ assert_eq!(&json! { IntPOST(5) }.to_string(), "5");
537+
538+ assert_eq!(
539+ BoolPOST(true),
540+ serde_json::from_str::<BoolPOST>("true").unwrap()
541+ );
542+ assert_eq!(
543+ BoolPOST(true),
544+ serde_json::from_str::<BoolPOST>("\"true\"").unwrap()
545+ );
546+ assert_eq!(&json! { BoolPOST(false) }.to_string(), "false");
547+ }
548+
549+ #[test]
550+ fn test_next() {
551+ let next = Next {
552+ next: Some("foo".to_string()),
553+ };
554+ assert_eq!(
555+ format!("{:?}", Redirect::to("foo")),
556+ format!("{:?}", next.or_else(|| "bar".to_string()))
557+ );
558+ let next = Next { next: None };
559+ assert_eq!(
560+ format!("{:?}", Redirect::to("bar")),
561+ format!("{:?}", next.or_else(|| "bar".to_string()))
562+ );
563+ }
564+ }