1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![warn(clippy::cognitive_complexity)]
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_time_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
#![allow(clippy::collapsible_if)] // See arti#2342
46
#![deny(clippy::unused_async)]
47
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48

            
49
use std::fmt;
50
use std::ops::{RangeInclusive, RangeToInclusive};
51
use std::path::Path;
52
use std::time::Duration;
53

            
54
pub mod error_sources;
55
pub mod intern;
56
pub mod iter;
57
pub mod n_key_list;
58
pub mod n_key_set;
59
pub mod rand_hostname;
60
pub mod rangebounds;
61
pub mod retry;
62
pub mod test_rng;
63

            
64
mod byte_qty;
65
pub use byte_qty::ByteQty;
66

            
67
pub use paste::paste;
68

            
69
use rand::Rng;
70

            
71
/// Sealed
72
mod sealed {
73
    /// Sealed
74
    pub trait Sealed {}
75
}
76
use sealed::Sealed;
77

            
78
// ----------------------------------------------------------------------
79

            
80
/// Function with the signature of `Debug::fmt` that just prints `".."`
81
///
82
/// ```
83
/// use educe::Educe;
84
/// use tor_basic_utils::skip_fmt;
85
///
86
/// #[derive(Educe, Default)]
87
/// #[educe(Debug)]
88
/// struct Wombat {
89
///     visible: usize,
90
///
91
///     #[educe(Debug(method = "skip_fmt"))]
92
///     invisible: [u8; 2],
93
/// }
94
///
95
/// assert_eq!( format!("{:?}", &Wombat::default()),
96
///             "Wombat { visible: 0, invisible: .. }" );
97
/// ```
98
15222
pub fn skip_fmt<T>(_: &T, f: &mut fmt::Formatter) -> fmt::Result {
99
    /// Inner function avoids code bloat due to generics
100
15222
    fn inner(f: &mut fmt::Formatter) -> fmt::Result {
101
15222
        write!(f, "..")
102
15222
    }
103
15222
    inner(f)
104
15222
}
105

            
106
// ----------------------------------------------------------------------
107

            
108
/// Formats an iterator as an object whose display implementation is a `separator`-separated string
109
/// of items from `iter`.
110
6
pub fn iter_join(
111
6
    separator: &str,
112
6
    iter: impl Iterator<Item: fmt::Display> + Clone,
113
6
) -> impl fmt::Display {
114
    // TODO: This can be replaced with `std::fmt::from_fn()` once stabilised and within our MSRV.
115
    struct Fmt<'a, I: Iterator<Item: fmt::Display> + Clone> {
116
        /// Separates items in `iter`.
117
        separator: &'a str,
118
        /// Iterator to join.
119
        iter: I,
120
    }
121
    impl<'a, I: Iterator<Item: fmt::Display> + Clone> fmt::Display for Fmt<'a, I> {
122
6
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123
6
            let Self { separator, iter } = self;
124
6
            let mut iter = iter.clone();
125
6
            if let Some(first) = iter.next() {
126
6
                write!(f, "{first}")?;
127
            }
128
12
            for x in iter {
129
6
                write!(f, "{separator}{x}")?;
130
            }
131
6
            Ok(())
132
6
        }
133
    }
134
6
    Fmt { separator, iter }
135
6
}
136

            
137
// ----------------------------------------------------------------------
138

            
139
/// Extension trait to provide `.strip_suffix_ignore_ascii_case()` etc.
140
// Using `.as_ref()` as a supertrait lets us make the method a provided one.
141
pub trait StrExt: AsRef<str> {
142
    /// Like `str.strip_suffix()` but ASCII-case-insensitive
143
6654
    fn strip_suffix_ignore_ascii_case(&self, suffix: &str) -> Option<&str> {
144
6654
        let whole = self.as_ref();
145
6654
        let suffix_start = whole.len().checked_sub(suffix.len())?;
146
6585
        whole[suffix_start..]
147
6585
            .eq_ignore_ascii_case(suffix)
148
6585
            .then(|| &whole[..suffix_start])
149
6654
    }
150

            
151
    /// Like `str.ends_with()` but ASCII-case-insensitive
152
82
    fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
153
82
        self.strip_suffix_ignore_ascii_case(suffix).is_some()
154
82
    }
155
}
156
impl StrExt for str {}
157

            
158
// ----------------------------------------------------------------------
159

            
160
/// Extension trait to provide `.gen_range_checked()`
161
pub trait RngExt: Rng {
162
    /// Generate a random value in the given range.
163
    ///
164
    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
165
    ///
166
    /// If the supplied range is empty, returns `None`.
167
    ///
168
    /// (This is a non-panicking version of [`Rng::gen_range`].)
169
    ///
170
    /// ### Example
171
    ///
172
    /// ```
173
    /// use tor_basic_utils::RngExt as _;
174
    //
175
    // Fake plastic imitation tor_error, since that's actually higher up the stack
176
    /// # #[macro_use]
177
    /// # mod tor_error {
178
    /// #     #[derive(Debug)]
179
    /// #     pub struct Bug;
180
    /// #     pub fn internal() {} // makes `use` work
181
    /// # }
182
    /// # macro_rules! internal { { $x:expr } => { Bug } }
183
    //
184
    /// use tor_error::{Bug, internal};
185
    ///
186
    /// fn choose(slice: &[i32]) -> Result<i32, Bug> {
187
    ///     let index = rand::rng()
188
    ///         .gen_range_checked(0..slice.len())
189
    ///         .ok_or_else(|| internal!("empty slice"))?;
190
    ///     Ok(slice[index])
191
    /// }
192
    ///
193
    /// assert_eq!(choose(&[42]).unwrap(), 42);
194
    /// let _: Bug = choose(&[]).unwrap_err();
195
    /// ```
196
    //
197
    // TODO: We may someday wish to rename this function to random_range_checked,
198
    // since gen_range was renamed to random_range in rand 0.9.
199
    // Or we might decide to leave it alone.
200
434456
    fn gen_range_checked<T, R>(&mut self, range: R) -> Option<T>
201
434456
    where
202
434456
        T: rand::distr::uniform::SampleUniform,
203
434456
        R: rand::distr::uniform::SampleRange<T>,
204
    {
205
434456
        if range.is_empty() {
206
            None
207
        } else {
208
            #[allow(clippy::disallowed_methods)]
209
434456
            Some(Rng::random_range(self, range))
210
        }
211
434456
    }
212

            
213
    /// Generate a random value in the given upper-bounded-only range.
214
    ///
215
    /// For use with an inclusive upper-bounded-only range,
216
    /// with types that implement `GenRangeInfallible`
217
    /// (that necessarily then implement the appropriate `rand` traits).
218
    ///
219
    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
220
    ///
221
    /// ### Example
222
    ///
223
    /// ```
224
    /// use std::time::Duration;
225
    /// use tor_basic_utils::RngExt as _;
226
    ///
227
    /// fn stochastic_sleep(max: Duration) {
228
    ///     let chosen_delay = rand::rng()
229
    ///         .gen_range_infallible(..=max);
230
    ///     std::thread::sleep(chosen_delay);
231
    /// }
232
    /// ```
233
306738
    fn gen_range_infallible<T>(&mut self, range: RangeToInclusive<T>) -> T
234
306738
    where
235
306738
        T: GenRangeInfallible,
236
    {
237
306738
        self.gen_range_checked(T::lower_bound()..=range.end)
238
306738
            .expect("GenRangeInfallible type with an empty lower_bound()..=T range")
239
306738
    }
240
}
241
impl<T: Rng> RngExt for T {}
242

            
243
/// Types that can be infallibly sampled using `gen_range_infallible`
244
///
245
/// In addition to the supertraits, the implementor of this trait must guarantee that:
246
///
247
/// `<Self as GenRangeInfallible>::lower_bound() ..= UPPER`
248
/// is a nonempty range for every value of `UPPER`.
249
//
250
// One might think that this trait is wrong because we might want to be able to
251
// implement gen_range_infallible for arguments other than RangeToInclusive<T>.
252
// However, double-ended ranges are inherently fallible because the actual values
253
// might be in the wrong order.  Non-inclusive ranges are fallible because the
254
// upper bound might be zero, unless a NonZero type is used, which seems like a further
255
// complication that we probably don't want to introduce here.  That leaves lower-bounded
256
// ranges, but those are very rare.
257
pub trait GenRangeInfallible: rand::distr::uniform::SampleUniform + Ord
258
where
259
    RangeInclusive<Self>: rand::distr::uniform::SampleRange<Self>,
260
{
261
    /// The usual lower bound, for converting a `RangeToInclusive` to a `RangeInclusive`
262
    ///
263
    /// Only makes sense with types with a sensible lower bound, such as zero.
264
    fn lower_bound() -> Self;
265
}
266

            
267
impl GenRangeInfallible for Duration {
268
417588
    fn lower_bound() -> Self {
269
417588
        Duration::ZERO
270
417588
    }
271
}
272

            
273
// ----------------------------------------------------------------------
274

            
275
/// Renaming of `Path::display` as `display_lossy`
276
pub trait PathExt: Sealed {
277
    /// Display this `Path` as an approximate string, for human consumption in messages
278
    ///
279
    /// Operating system paths cannot always be faithfully represented as Rust strings,
280
    /// because they might not be valid Unicode.
281
    ///
282
    /// This helper method provides a way to display a string for human users.
283
    /// **This may lose information** so should only be used for error messages etc.
284
    ///
285
    /// This method is exactly the same as [`std::path::Path::display`],
286
    /// but with a different and more discouraging name.
287
    fn display_lossy(&self) -> std::path::Display<'_>;
288
}
289
impl Sealed for Path {}
290
impl PathExt for Path {
291
    #[allow(clippy::disallowed_methods)]
292
2709
    fn display_lossy(&self) -> std::path::Display<'_> {
293
2709
        self.display()
294
2709
    }
295
}
296

            
297
// ----------------------------------------------------------------------
298

            
299
/// Define an "accessor trait", which describes structs that have fields of certain types
300
///
301
/// This can be useful if a large struct, living high up in the dependency graph,
302
/// contains fields that lower-lever crates want to be able to use without having
303
/// to copy the data about etc.
304
///
305
/// ```
306
/// // imagine this in the lower-level module
307
/// pub trait Supertrait {}
308
/// use tor_basic_utils::define_accessor_trait;
309
/// define_accessor_trait! {
310
///     pub trait View: Supertrait {
311
///         lorem: String,
312
///         ipsum: usize,
313
///         +
314
///         fn other_accessor(&self) -> bool;
315
///         // any other trait items can go here
316
///    }
317
/// }
318
///
319
/// fn test_view<V: View>(v: &V) {
320
///     assert_eq!(v.lorem(), "sit");
321
///     assert_eq!(v.ipsum(), &42);
322
/// }
323
///
324
/// // imagine this in the higher-level module
325
/// use derive_more::AsRef;
326
/// #[derive(AsRef)]
327
/// struct Everything {
328
///     #[as_ref] lorem: String,
329
///     #[as_ref] ipsum: usize,
330
///     dolor: Vec<()>,
331
/// }
332
/// impl Supertrait for Everything { }
333
/// impl View for Everything {
334
///     fn other_accessor(&self) -> bool { false }
335
/// }
336
///
337
/// let everything = Everything {
338
///     lorem: "sit".into(),
339
///     ipsum: 42,
340
///     dolor: vec![()],
341
/// };
342
///
343
/// test_view(&everything);
344
/// ```
345
///
346
/// ### Generated code
347
///
348
/// ```
349
/// # pub trait Supertrait { }
350
/// pub trait View: AsRef<String> + AsRef<usize> + Supertrait {
351
///     fn lorem(&self) -> &String { self.as_ref() }
352
///     fn ipsum(&self) -> &usize { self.as_ref() }
353
/// }
354
/// ```
355
#[macro_export]
356
macro_rules! define_accessor_trait {
357
    {
358
        $( #[ $attr:meta ])*
359
        $vis:vis trait $Trait:ident $( : $( $Super:path )* )? {
360
            $( $accessor:ident: $type:ty, )*
361
            $( + $( $rest:tt )* )?
362
        }
363
    } => {
364
        $( #[ $attr ])*
365
        $vis trait $Trait: $( core::convert::AsRef<$type> + )* $( $( $Super + )* )?
366
        {
367
            $(
368
                /// Access the field
369
848
                fn $accessor(&self) -> &$type { core::convert::AsRef::as_ref(self) }
370
            )*
371
            $(
372
                $( $rest )*
373
            )?
374
        }
375
    }
376
}
377

            
378
// ----------------------------------------------------------------------
379

            
380
/// Helper for assisting with macro "argument" defaulting
381
///
382
/// ```ignore
383
/// macro_coalesce_args!{ [ something ]  ... }  // =>   something
384
/// macro_coalesce_args!{ [ ], [ other ] ... }  // =>   other
385
/// // etc.
386
/// ```
387
///
388
/// ### Usage note
389
///
390
/// It is generally possible to avoid use of `macro_coalesce_args`, at the cost of
391
/// providing many alternative matcher patterns.  Using `macro_coalesce_args` can make
392
/// it possible to provide a single pattern with the optional items in `$( )?`.
393
///
394
/// This is valuable because a single pattern with some optional items
395
/// makes much better documentation than several patterns which the reader must compare
396
/// by eye - and it also simplifies the implementation.
397
///
398
/// `macro_coalesce_args` takes each of its possible expansions in `[ ]` and returns
399
/// the first nonempty one.
400
#[macro_export]
401
macro_rules! macro_first_nonempty {
402
    { [ $($yes:tt)+ ] $($rhs:tt)* } => { $($yes)* };
403
    { [ ]$(,)? [ $($otherwise:tt)* ] $($rhs:tt)* } => {
404
        $crate::macro_first_nonempty!{ [ $($otherwise)* ] $($rhs)* }
405
    };
406
}
407

            
408
// ----------------------------------------------------------------------
409

            
410
/// Define `Debug` to print as hex
411
///
412
/// # Usage
413
///
414
/// ```ignore
415
/// impl_debug_hex! { $type }
416
/// impl_debug_hex! { $type . $field_accessor }
417
/// impl_debug_hex! { $type , $accessor_fn }
418
/// ```
419
///
420
/// By default, this expects `$type` to implement `AsRef<[u8]>`.
421
///
422
/// Or, you can supply a series of tokens `$field_accessor`,
423
/// which will be used like this: `self.$field_accessor.as_ref()`
424
/// to get a `&[u8]`.
425
///
426
/// Or, you can supply `$accessor: fn(&$type) -> &[u8]`.
427
///
428
/// # Examples
429
///
430
/// ```
431
/// use tor_basic_utils::impl_debug_hex;
432
/// #[derive(Default)]
433
/// struct FourBytes([u8; 4]);
434
/// impl AsRef<[u8]> for FourBytes { fn as_ref(&self) -> &[u8] { &self.0 } }
435
/// impl_debug_hex! { FourBytes }
436
///
437
/// assert_eq!(
438
///     format!("{:?}", FourBytes::default()),
439
///     "FourBytes(00000000)",
440
/// );
441
/// ```
442
///
443
/// ```
444
/// use tor_basic_utils::impl_debug_hex;
445
/// #[derive(Default)]
446
/// struct FourBytes([u8; 4]);
447
/// impl_debug_hex! { FourBytes .0 }
448
///
449
/// assert_eq!(
450
///     format!("{:?}", FourBytes::default()),
451
///     "FourBytes(00000000)",
452
/// );
453
/// ```
454
///
455
/// ```
456
/// use tor_basic_utils::impl_debug_hex;
457
/// struct FourBytes([u8; 4]);
458
/// impl_debug_hex! { FourBytes, |self_| &self_.0 }
459
///
460
/// assert_eq!(
461
///     format!("{:?}", FourBytes([1,2,3,4])),
462
///     "FourBytes(01020304)",
463
/// )
464
/// ```
465
#[macro_export]
466
macro_rules! impl_debug_hex {
467
    { $type:ty $(,)? } => {
468
        $crate::impl_debug_hex! { $type, |self_| <$type as AsRef<[u8]>>::as_ref(&self_) }
469
    };
470
    { $type:ident . $($accessor:tt)+ } => {
471
161
        $crate::impl_debug_hex! { $type, |self_| self_ . $($accessor)* .as_ref() }
472
    };
473
    { $type:ty, $obtain:expr $(,)? } => {
474
        impl std::fmt::Debug for $type {
475
161
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
476
                use std::fmt::Write;
477
161
                let obtain: fn(&$type) -> &[u8] = $obtain;
478
161
                let bytes: &[u8] = obtain(self);
479
161
                write!(f, "{}(", stringify!($type))?;
480
5313
                for b in bytes {
481
5152
                    write!(f, "{:02x}", b)?;
482
                }
483
161
                write!(f, ")")?;
484
161
                Ok(())
485
161
            }
486
        }
487
    };
488
}
489

            
490
// ----------------------------------------------------------------------
491

            
492
/// Helper for defining a struct which can be (de)serialized several ways, including "natively"
493
///
494
/// Ideally we would have
495
/// ```rust ignore
496
/// #[derive(Deserialize)]
497
/// #[serde(try_from=Possibilities)]
498
/// struct Main { /* principal definition */ }
499
///
500
/// #[derive(Deserialize)]
501
/// #[serde(untagged)]
502
/// enum Possibilities { Main(Main), Other(OtherRepr) }
503
///
504
/// #[derive(Deserialize)]
505
/// struct OtherRepr { /* other representation we still want to read */ }
506
///
507
/// impl TryFrom<Possibilities> for Main { /* ... */ }
508
/// ```
509
///
510
/// But the impl for `Possibilities` ends up honouring the `try_from` on `Main`
511
/// so is recursive.
512
///
513
/// We solve that (ab)using serde's remote feature,
514
/// on a second copy of the struct definition.
515
///
516
/// See the Example for instructions.
517
/// It is important to **add test cases**
518
/// for all the representations you expect to parse and serialise,
519
/// since there are easy-to-write bugs,
520
/// for example omitting some of the necessary attributes.
521
///
522
/// # Generated output:
523
///
524
///  * The original struct definition, unmodified
525
///  * `#[derive(Serialize, Deserialize)] struct $main_Raw { }`
526
///
527
/// The `$main_Raw` struct ought not normally be to constructed anywhere,
528
/// and *isn't* convertible to or from the near-identical `$main` struct.
529
/// It exists only as a thing to feed to the serde remove derive,
530
/// and name in `with=`.
531
///
532
/// # Example
533
///
534
/// ```
535
/// use serde::{Deserialize, Serialize};
536
/// use tor_basic_utils::derive_serde_raw;
537
///
538
/// derive_serde_raw! {
539
///     #[derive(Deserialize, Serialize, Default, Clone, Debug)]
540
///     #[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
541
///     pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
542
///         transport: Option<String>,
543
///         //...
544
///     }
545
/// }
546
///
547
/// #[derive(Serialize,Deserialize)]
548
/// #[serde(untagged)]
549
/// enum BridgeConfigBuilderSerde {
550
///     BridgeLine(String),
551
///     Dict(#[serde(with="BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
552
/// }
553
///
554
/// impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder { //...
555
/// #    type Error = std::io::Error;
556
/// #    fn try_from(_: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> { todo!() } }
557
/// impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde { //...
558
/// #    fn from(_: BridgeConfigBuilder) -> BridgeConfigBuilderSerde { todo!() } }
559
/// ```
560
#[macro_export]
561
macro_rules! derive_serde_raw { {
562
    $( #[ $($attrs:meta)* ] )*
563
    $vis:vis struct $main:ident=$main_s:literal
564
    $($body:tt)*
565
} => {
566
    $(#[ $($attrs)* ])*
567
    $vis struct $main
568
    $($body)*
569

            
570
    $crate::paste! {
571
        #[allow(non_camel_case_types)]
572
        #[derive(Serialize, Deserialize)]
573
        #[serde(remote=$main_s)]
574
        struct [< $main _Raw >]
575
        $($body)*
576
    }
577
} }
578

            
579
// ----------------------------------------------------------------------
580

            
581
/// Flatten a `Result<Result<T, E>, E>` into a `Result<T, E>`.
582
///
583
/// See [`Result::flatten`], which is not available
584
/// at our current MSRV.
585
// TODO MSRV 1.89: When our MSRV is at least 1.89,
586
// remove this function and replace uses with `Result::flatten`.
587
pub fn flatten<T, E>(x: Result<Result<T, E>, E>) -> Result<T, E> {
588
    match x {
589
        Ok(Ok(x)) => Ok(x),
590
        Err(e) | Ok(Err(e)) => Err(e),
591
    }
592
}
593

            
594
// ----------------------------------------------------------------------
595

            
596
/// Asserts that the type of the expression implements the given trait.
597
///
598
/// Example:
599
///
600
/// ```
601
/// # use tor_basic_utils::assert_val_impl_trait;
602
/// let x: u32 = 0;
603
/// assert_val_impl_trait!(x, Clone);
604
/// ```
605
#[macro_export]
606
macro_rules! assert_val_impl_trait {
607
    ($check:expr, $trait:path $(,)?) => {{
608
240
        fn ensure_trait<T: $trait>(_s: &T) {}
609
        ensure_trait(&$check);
610
    }};
611
}
612

            
613
// ----------------------------------------------------------------------
614

            
615
#[cfg(test)]
616
mod test {
617
    // @@ begin test lint list maintained by maint/add_warning @@
618
    #![allow(clippy::bool_assert_comparison)]
619
    #![allow(clippy::clone_on_copy)]
620
    #![allow(clippy::dbg_macro)]
621
    #![allow(clippy::mixed_attributes_style)]
622
    #![allow(clippy::print_stderr)]
623
    #![allow(clippy::print_stdout)]
624
    #![allow(clippy::single_char_pattern)]
625
    #![allow(clippy::unwrap_used)]
626
    #![allow(clippy::unchecked_time_subtraction)]
627
    #![allow(clippy::useless_vec)]
628
    #![allow(clippy::needless_pass_by_value)]
629
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
630
    use super::*;
631

            
632
    #[test]
633
    fn test_strip_suffix_ignore_ascii_case() {
634
        assert_eq!(
635
            "hi there".strip_suffix_ignore_ascii_case("THERE"),
636
            Some("hi ")
637
        );
638
        assert_eq!("hi here".strip_suffix_ignore_ascii_case("THERE"), None);
639
        assert_eq!("THERE".strip_suffix_ignore_ascii_case("there"), Some(""));
640
        assert_eq!("hi".strip_suffix_ignore_ascii_case("THERE"), None);
641
    }
642
}