1
#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_duration_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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45

            
46
use std::collections::BinaryHeap;
47
use std::fmt;
48
use std::mem;
49
use std::ops::{RangeInclusive, RangeToInclusive};
50
use std::path::Path;
51
use std::time::Duration;
52

            
53
pub mod iter;
54
pub mod n_key_list;
55
pub mod n_key_set;
56
pub mod rangebounds;
57
pub mod retry;
58
pub mod test_rng;
59

            
60
mod byte_qty;
61
pub use byte_qty::ByteQty;
62

            
63
pub use paste::paste;
64

            
65
use rand::Rng;
66

            
67
/// Sealed
68
mod sealed {
69
    /// Sealed
70
    pub trait Sealed {}
71
}
72
use sealed::Sealed;
73

            
74
// ----------------------------------------------------------------------
75

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

            
102
// ----------------------------------------------------------------------
103

            
104
/// Extension trait to provide `.strip_suffix_ignore_ascii_case()` etc.
105
// Using `.as_ref()` as a supertrait lets us make the method a provided one.
106
pub trait StrExt: AsRef<str> {
107
    /// Like `str.strip_suffix()` but ASCII-case-insensitive
108
3977
    fn strip_suffix_ignore_ascii_case(&self, suffix: &str) -> Option<&str> {
109
3977
        let whole = self.as_ref();
110
3977
        let suffix_start = whole.len().checked_sub(suffix.len())?;
111
3914
        whole[suffix_start..]
112
3914
            .eq_ignore_ascii_case(suffix)
113
3914
            .then(|| &whole[..suffix_start])
114
3977
    }
115

            
116
    /// Like `str.ends_with()` but ASCII-case-insensitive
117
78
    fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
118
78
        self.strip_suffix_ignore_ascii_case(suffix).is_some()
119
78
    }
120
}
121
impl StrExt for str {}
122

            
123
// ----------------------------------------------------------------------
124

            
125
/// Extension trait to provide `.gen_range_checked()`
126
pub trait RngExt: Rng {
127
    /// Generate a random value in the given range.
128
    ///
129
    /// 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.
130
    ///
131
    /// If the supplied range is empty, returns `None`.
132
    ///
133
    /// (This is a non-panicking version of [`Rng::gen_range`].)
134
    ///
135
    /// ### Example
136
    ///
137
    /// ```
138
    /// use tor_basic_utils::RngExt as _;
139
    //
140
    // Fake plastic imitation tor_error, since that's actually higher up the stack
141
    /// # #[macro_use]
142
    /// # mod tor_error {
143
    /// #     #[derive(Debug)]
144
    /// #     pub struct Bug;
145
    /// #     pub fn internal() {} // makes `use` work
146
    /// # }
147
    /// # macro_rules! internal { { $x:expr } => { Bug } }
148
    //
149
    /// use tor_error::{Bug, internal};
150
    ///
151
    /// fn choose(slice: &[i32]) -> Result<i32, Bug> {
152
    ///     let index = rand::rng()
153
    ///         .gen_range_checked(0..slice.len())
154
    ///         .ok_or_else(|| internal!("empty slice"))?;
155
    ///     Ok(slice[index])
156
    /// }
157
    ///
158
    /// assert_eq!(choose(&[42]).unwrap(), 42);
159
    /// let _: Bug = choose(&[]).unwrap_err();
160
    /// ```
161
    //
162
    // TODO: We may someday wish to rename this function to random_range_checked,
163
    // since gen_range was renamed to random_range in rand 0.9.
164
    // Or we might decide to leave it alone.
165
386456
    fn gen_range_checked<T, R>(&mut self, range: R) -> Option<T>
166
386456
    where
167
386456
        T: rand::distr::uniform::SampleUniform,
168
386456
        R: rand::distr::uniform::SampleRange<T>,
169
386456
    {
170
386456
        if range.is_empty() {
171
            None
172
        } else {
173
            #[allow(clippy::disallowed_methods)]
174
386456
            Some(Rng::random_range(self, range))
175
        }
176
386456
    }
177

            
178
    /// Generate a random value in the given upper-bounded-only range.
179
    ///
180
    /// For use with an inclusive upper-bounded-only range,
181
    /// with types that implement `GenRangeInfallible`
182
    /// (that necessarily then implement the appropriate `rand` traits).
183
    ///
184
    /// 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.
185
    ///
186
    /// ### Example
187
    ///
188
    /// ```
189
    /// use std::time::Duration;
190
    /// use tor_basic_utils::RngExt as _;
191
    ///
192
    /// fn stochastic_sleep(max: Duration) {
193
    ///     let chosen_delay = rand::rng()
194
    ///         .gen_range_infallible(..=max);
195
    ///     std::thread::sleep(chosen_delay);
196
    /// }
197
    /// ```
198
280307
    fn gen_range_infallible<T>(&mut self, range: RangeToInclusive<T>) -> T
199
280307
    where
200
280307
        T: GenRangeInfallible,
201
280307
    {
202
280307
        self.gen_range_checked(T::lower_bound()..=range.end)
203
280307
            .expect("GenRangeInfallible type with an empty lower_bound()..=T range")
204
280307
    }
205
}
206
impl<T: Rng> RngExt for T {}
207

            
208
/// Types that can be infallibly sampled using `gen_range_infallible`
209
///
210
/// In addition to the supertraits, the implementor of this trait must guarantee that:
211
///
212
/// `<Self as GenRangeInfallible>::lower_bound() ..= UPPER`
213
/// is a nonempty range for every value of `UPPER`.
214
//
215
// One might think that this trait is wrong because we might want to be able to
216
// implement gen_range_infallible for arguments other than RangeToInclusive<T>.
217
// However, double-ended ranges are inherently fallible because the actual values
218
// might be in the wrong order.  Non-inclusive ranges are fallible because the
219
// upper bound might be zero, unless a NonZero type is used, which seems like a further
220
// complication that we probably don't want to introduce here.  That leaves lower-bounded
221
// ranges, but those are very rare.
222
pub trait GenRangeInfallible: rand::distr::uniform::SampleUniform + Ord
223
where
224
    RangeInclusive<Self>: rand::distr::uniform::SampleRange<Self>,
225
{
226
    /// The usual lower bound, for converting a `RangeToInclusive` to a `RangeInclusive`
227
    ///
228
    /// Only makes sense with types with a sensible lower bound, such as zero.
229
    fn lower_bound() -> Self;
230
}
231

            
232
impl GenRangeInfallible for Duration {
233
373758
    fn lower_bound() -> Self {
234
373758
        Duration::ZERO
235
373758
    }
236
}
237

            
238
// ----------------------------------------------------------------------
239

            
240
/// Implementation of `ErrorKind::NotADirectory` that doesn't require Nightly
241
pub trait IoErrorExt: Sealed {
242
    /// Is this `io::ErrorKind::NotADirectory` ?
243
    fn is_not_a_directory(&self) -> bool;
244
}
245
impl Sealed for std::io::Error {}
246
impl IoErrorExt for std::io::Error {
247
    fn is_not_a_directory(&self) -> bool {
248
        self.raw_os_error()
249
            == Some(
250
                #[cfg(target_family = "unix")]
251
                libc::ENOTDIR,
252
                #[cfg(target_family = "windows")]
253
                {
254
                    /// Obtained from Rust stdlib source code
255
                    /// See also:
256
                    ///   <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499->
257
                    /// (although the documentation is anaemic) and
258
                    /// <https://github.com/rust-lang/rust/pull/79965>
259
                    const ERROR_DIRECTORY: i32 = 267;
260
                    ERROR_DIRECTORY
261
                },
262
            )
263
    }
264
}
265

            
266
// ----------------------------------------------------------------------
267

            
268
/// Implementation of `BinaryHeap::retain` that doesn't require Nightly
269
pub trait BinaryHeapExt<T> {
270
    /// Remove all elements for which `f` returns `false`
271
    ///
272
    /// Performance is not great right now - the algorithm is `O(n*log(n))`
273
    /// where `n` is the number of elements in the heap (not the number removed).
274
    ///
275
    /// The name is `retain_ext` to avoid a name collision with the unstable function,
276
    /// which would require the use of UFCS and make this unergonomic.
277
    fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F);
278
}
279
impl<T: Ord> BinaryHeapExt<T> for BinaryHeap<T> {
280
88
    fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F) {
281
88
        let items = mem::take(self).into_iter();
282
88
        *self = items.filter(f).collect();
283
88
    }
284
}
285

            
286
// ----------------------------------------------------------------------
287

            
288
/// Renaming of `Path::display` as `display_lossy`
289
pub trait PathExt: Sealed {
290
    /// Display this `Path` as an approximate string, for human consumption in messages
291
    ///
292
    /// Operating system paths cannot always be faithfully represented as Rust strings,
293
    /// because they might not be valid Unicode.
294
    ///
295
    /// This helper method provides a way to display a string for human users.
296
    /// **This may lose information** so should only be used for error messages etc.
297
    ///
298
    /// This method is exactly the same as [`std::path::Path::display`],
299
    /// but with a different and more discouraging name.
300
    fn display_lossy(&self) -> std::path::Display<'_>;
301
}
302
impl Sealed for Path {}
303
impl PathExt for Path {
304
    #[allow(clippy::disallowed_methods)]
305
2117
    fn display_lossy(&self) -> std::path::Display<'_> {
306
2117
        self.display()
307
2117
    }
308
}
309

            
310
// ----------------------------------------------------------------------
311

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

            
391
// ----------------------------------------------------------------------
392

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

            
421
// ----------------------------------------------------------------------
422

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

            
503
// ----------------------------------------------------------------------
504

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

            
583
    $crate::paste! {
584
        #[allow(non_camel_case_types)]
585
        #[derive(Serialize, Deserialize)]
586
        #[serde(remote=$main_s)]
587
        struct [< $main _Raw >]
588
        $($body)*
589
    }
590
} }
591

            
592
// ----------------------------------------------------------------------
593

            
594
/// Flatten a `Result<Result<T, E>, E>` into a `Result<T, E>`.
595
///
596
/// See the nightly [`Result::flatten`].
597
// TODO MSRV TBD: When `Result::flatten` is stable and our MSRV allows,
598
// remove this function and replace uses with `Result::flatten`.
599
8742
pub fn flatten<T, E>(x: Result<Result<T, E>, E>) -> Result<T, E> {
600
8742
    match x {
601
8742
        Ok(Ok(x)) => Ok(x),
602
        Err(e) | Ok(Err(e)) => Err(e),
603
    }
604
8742
}
605

            
606
// ----------------------------------------------------------------------
607

            
608
#[cfg(test)]
609
mod test {
610
    // @@ begin test lint list maintained by maint/add_warning @@
611
    #![allow(clippy::bool_assert_comparison)]
612
    #![allow(clippy::clone_on_copy)]
613
    #![allow(clippy::dbg_macro)]
614
    #![allow(clippy::mixed_attributes_style)]
615
    #![allow(clippy::print_stderr)]
616
    #![allow(clippy::print_stdout)]
617
    #![allow(clippy::single_char_pattern)]
618
    #![allow(clippy::unwrap_used)]
619
    #![allow(clippy::unchecked_duration_subtraction)]
620
    #![allow(clippy::useless_vec)]
621
    #![allow(clippy::needless_pass_by_value)]
622
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
623
    use super::*;
624

            
625
    #[test]
626
    fn test_strip_suffix_ignore_ascii_case() {
627
        assert_eq!(
628
            "hi there".strip_suffix_ignore_ascii_case("THERE"),
629
            Some("hi ")
630
        );
631
        assert_eq!("hi here".strip_suffix_ignore_ascii_case("THERE"), None);
632
        assert_eq!("THERE".strip_suffix_ignore_ascii_case("there"), Some(""));
633
        assert_eq!("hi".strip_suffix_ignore_ascii_case("THERE"), None);
634
    }
635
}