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
pub mod cmdline;
47
mod err;
48
#[macro_use]
49
pub mod extend_builder;
50
pub mod file_watcher;
51
mod flatten;
52
pub mod list_builder;
53
mod listen;
54
pub mod load;
55
pub mod map_builder;
56
mod misc;
57
pub mod mistrust;
58
mod mut_cfg;
59
pub mod sources;
60
#[cfg(feature = "testing")]
61
pub mod testing;
62

            
63
#[doc(hidden)]
64
pub mod deps {
65
    pub use educe;
66
    pub use figment;
67
    pub use itertools::Itertools;
68
    pub use paste::paste;
69
    pub use serde;
70
    pub use serde_value;
71
    pub use tor_basic_utils::macro_first_nonempty;
72
}
73

            
74
pub use cmdline::CmdLine;
75
pub use err::{ConfigBuildError, ConfigError, ReconfigureError};
76
pub use flatten::{Flatten, Flattenable};
77
pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
78
pub use listen::*;
79
pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
80
pub use misc::*;
81
pub use mut_cfg::MutCfg;
82
pub use sources::{ConfigurationSource, ConfigurationSources};
83

            
84
use itertools::Itertools;
85

            
86
#[doc(hidden)]
87
pub use derive_deftly;
88
#[doc(hidden)]
89
pub use flatten::flattenable_extract_fields;
90

            
91
derive_deftly::template_export_semver_check! { "0.12.1" }
92

            
93
/// A set of configuration fields, represented as a set of nested K=V
94
/// mappings.
95
///
96
/// (This is a wrapper for an underlying type provided by the library that
97
/// actually does our configuration.)
98
#[derive(Clone, Debug)]
99
pub struct ConfigurationTree(figment::Figment);
100

            
101
#[cfg(test)]
102
impl ConfigurationTree {
103
    #[cfg(test)]
104
14
    pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
105
        use figment::value::Value as V;
106
14
        let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
107
12
        Ok(match val {
108
8
            V::String(_, s) => s.to_string(),
109
4
            V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
110
            _ => format!("{:?}", val),
111
        })
112
14
    }
113
}
114

            
115
/// Rules for reconfiguring a running Arti instance.
116
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
117
#[non_exhaustive]
118
pub enum Reconfigure {
119
    /// Perform no reconfiguration unless we can guarantee that all changes will be successful.
120
    AllOrNothing,
121
    /// Try to reconfigure as much as possible; warn on fields that we cannot reconfigure.
122
    WarnOnFailures,
123
    /// Don't reconfigure anything: Only check whether we can guarantee that all changes will be successful.
124
    CheckAllOrNothing,
125
}
126

            
127
impl Reconfigure {
128
    /// Called when we see a disallowed attempt to change `field`: either give a ReconfigureError,
129
    /// or warn and return `Ok(())`, depending on the value of `self`.
130
4
    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
131
4
        match self {
132
            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
133
2
                Err(ReconfigureError::CannotChange {
134
2
                    field: field.as_ref().to_owned(),
135
2
                })
136
            }
137
            Reconfigure::WarnOnFailures => {
138
2
                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
139
2
                Ok(())
140
            }
141
        }
142
4
    }
143
}
144

            
145
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
146
///
147
///  * If the input is `None`, this indicates that the user did not specify a value,
148
///    and we therefore use `def` to obtain the default value.
149
///
150
///  * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
151
///    the user has explicitly specified that this config item should be null/none/nothing,
152
///    so we return `None`.
153
///
154
///  * Otherwise the user provided an actual value, and we return `Some` of it.
155
///
156
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
157
///
158
/// For consistency with other APIs in Arti, when using this,
159
/// do not pass `setter(strip_option)` to derive_builder.
160
///
161
/// # âš  Stability Warning âš 
162
///
163
/// We may significantly change this so that it is an method in an extension trait.
164
//
165
// This is an annoying AOI right now because you have to write things like
166
//     #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
167
//     pub(crate) dns_port: Option<u16>,
168
// which recapitulates the field name.  That is very much a bug hazard (indeed, in an
169
// early version of some of this code I perpetrated precisely that bug).
170
// Fixing this involves a derive_builder feature.
171
176
pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
172
176
where
173
176
    T: Clone + Default + PartialEq,
174
176
    DF: FnOnce() -> Option<T>,
175
176
{
176
176
    resolve_option_general(
177
176
        input.as_ref().map(|ov| ov.as_ref()),
178
176
        |v| v == &T::default(),
179
176
        def,
180
176
    )
181
176
}
182

            
183
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`, more generally
184
///
185
/// Like `resolve_option`, but:
186
///
187
///  * Doesn't rely on `T`' being `Default + PartialEq`
188
///    to determine whether it's the sentinel value;
189
///    instead, taking `is_explicit`.
190
///
191
///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
192
///
193
///  * If the input is `None`, this indicates that the user did not specify a value,
194
///    and we therefore use `def` to obtain the default value.
195
///
196
///  * If the input is `Some(None)`, or `Some(Some(v)) where is_sentinel(v)`,
197
///    the user has explicitly specified that this config item should be null/none/nothing,
198
///    so we return `None`.
199
///
200
///  * Otherwise the user provided an actual value, and we return `Some` of it.
201
///
202
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
203
///
204
/// # âš  Stability Warning âš 
205
///
206
/// We may significantly change this so that it is an method in an extension trait.
207
//
208
// TODO: it would be nice to have an example here, but right now I'm not sure
209
// what type (or config setting) we could put in an example that would be natural enough
210
// to add clarity.  See
211
//  https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/685#note_2829951
212
176
pub fn resolve_option_general<T, ISF, DF>(
213
176
    input: Option<Option<&T>>,
214
176
    is_sentinel: ISF,
215
176
    def: DF,
216
176
) -> Option<T>
217
176
where
218
176
    T: Clone,
219
176
    DF: FnOnce() -> Option<T>,
220
176
    ISF: FnOnce(&T) -> bool,
221
176
{
222
12
    match input {
223
160
        None => def(),
224
4
        Some(None) => None,
225
12
        Some(Some(v)) if is_sentinel(v) => None,
226
4
        Some(Some(v)) => Some(v.clone()),
227
    }
228
176
}
229

            
230
/// Helper for resolving a config item which can be specified in multiple ways
231
///
232
/// Usable when a single configuration item can be specified
233
/// via multiple (alternative) input fields;
234
/// Each input field which is actually present
235
/// should be converted to the common output type,
236
/// and then passed to this function,
237
/// which will handle consistency checks and defaulting.
238
///
239
/// A common use case is deprecated field name/types.
240
/// In that case, the deprecated field names should be added to the appropriate
241
/// [`load::TopLevel::DEPRECATED_KEYS`].
242
///
243
/// `specified` should be an array (or other iterator) of `(key, Option<value>)`
244
/// where `key` is the field name and
245
/// `value` is that field from the builder,
246
/// converted to the common output type `V`.
247
///
248
/// # Example
249
///
250
/// ```
251
/// use derive_builder::Builder;
252
/// use serde::{Deserialize, Serialize};
253
/// use tor_config::{impl_standard_builder, ConfigBuildError, Listen, resolve_alternative_specs};
254
///
255
/// #[derive(Debug, Clone, Builder, Eq, PartialEq)]
256
/// #[builder(build_fn(error = "ConfigBuildError"))]
257
/// #[builder(derive(Debug, Serialize, Deserialize))]
258
/// #[allow(clippy::option_option)]
259
/// pub struct ProxyConfig {
260
///    /// Addresses to listen on for incoming SOCKS connections.
261
///    #[builder(field(build = r#"self.resolve_socks_port()?"#))]
262
///    pub(crate) socks_listen: Listen,
263
///
264
///    /// Port to listen on (at localhost) for incoming SOCKS
265
///    /// connections.
266
///    #[builder(setter(strip_option), field(type = "Option<Option<u16>>", build = "()"))]
267
///    pub(crate) socks_port: (),
268
/// }
269
/// impl_standard_builder! { ProxyConfig }
270
///
271
/// impl ProxyConfigBuilder {
272
///     fn resolve_socks_port(&self) -> Result<Listen, ConfigBuildError> {
273
///         resolve_alternative_specs(
274
///             [
275
///                 ("socks_listen", self.socks_listen.clone()),
276
///                 ("socks_port", self.socks_port.map(Listen::new_localhost_optional)),
277
///             ],
278
///             || Listen::new_localhost(9150),
279
///         )
280
///     }
281
/// }
282
/// ```
283
//
284
// Testing: this is tested quit exhaustively in the context of the listen/port handling, in
285
// crates/arti/src/cfg.rs.
286
338
pub fn resolve_alternative_specs<V, K>(
287
338
    specified: impl IntoIterator<Item = (K, Option<V>)>,
288
338
    default: impl FnOnce() -> V,
289
338
) -> Result<V, ConfigBuildError>
290
338
where
291
338
    K: Into<String>,
292
338
    V: Eq,
293
338
{
294
338
    Ok(specified
295
338
        .into_iter()
296
676
        .filter_map(|(k, v)| Some((k, v?)))
297
338
        .dedup_by(|(_, v1), (_, v2)| v1 == v2)
298
338
        .at_most_one()
299
338
        .map_err(|several| ConfigBuildError::Inconsistent {
300
24
            fields: several.into_iter().map(|(k, _v)| k.into()).collect_vec(),
301
12
            problem: "conflicting fields, specifying different values".into(),
302
338
        })?
303
326
        .map(|(_k, v)| v)
304
326
        .unwrap_or_else(default))
305
338
}
306

            
307
/// Defines standard impls for a struct with a `Builder`, incl `Default`
308
///
309
/// **Use this.**  Do not `#[derive(Builder, Default)]`.  That latter approach would produce
310
/// wrong answers if builder attributes are used to specify non-`Default` default values.
311
///
312
/// # Input syntax
313
///
314
/// ```
315
/// use derive_builder::Builder;
316
/// use serde::{Deserialize, Serialize};
317
/// use tor_config::impl_standard_builder;
318
/// use tor_config::ConfigBuildError;
319
///
320
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
321
/// #[builder(derive(Serialize, Deserialize, Debug))]
322
/// #[builder(build_fn(error = "ConfigBuildError"))]
323
/// struct SomeConfigStruct { }
324
/// impl_standard_builder! { SomeConfigStruct }
325
///
326
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
327
/// struct UnusualStruct { }
328
/// impl_standard_builder! { UnusualStruct: !Deserialize + !Builder }
329
/// ```
330
///
331
/// # Requirements
332
///
333
/// `$Config`'s builder must have default values for all the fields,
334
/// or this macro-generated self-test will fail.
335
/// This should be OK for all principal elements of our configuration.
336
///
337
/// `$ConfigBuilder` must have an appropriate `Deserialize` impl.
338
///
339
/// # Options
340
///
341
///  * `!Default` suppresses the `Default` implementation, and the corresponding tests.
342
///    This should be done within Arti's configuration only for sub-structures which
343
///    contain mandatory fields (and are themselves optional).
344
///
345
///  * `!Deserialize` suppresses the test case involving `Builder: Deserialize`.
346
///    This should not be done for structs which are part of Arti's configuration,
347
///    but can be appropriate for other types that use [`derive_builder`].
348
///
349
///  * `!Builder` suppresses the impl of the [`tor_config::load::Builder`](load::Builder) trait
350
///    This will be necessary if the error from the builder is not [`ConfigBuildError`].
351
///
352
/// # Generates
353
///
354
///  * `impl Default for $Config`
355
///  * `impl Builder for $ConfigBuilder`
356
///  * a self-test that the `Default` impl actually works
357
///  * a test that the `Builder` can be deserialized from an empty [`ConfigurationTree`],
358
///    and then built, and that the result is the same as the ordinary default.
359
//
360
// The implementation munches fake "trait bounds" (`: !Deserialize + !Wombat ...`) off the RHS.
361
// We're going to add at least one more option.
362
//
363
// When run with `!Default`, this only generates a `builder` impl and an impl of
364
// the `Resolvable` trait which probably won't be used anywhere.  That may seem
365
// like a poor tradeoff (much fiddly macro code to generate a trivial function in
366
// a handful of call sites).  However, this means that `impl_standard_builder!`
367
// can be used in more places.  That sets a good example: always use the macro.
368
//
369
// That is a good example because we want `impl_standard_builder!` to be
370
// used elsewhere because it generates necessary tests of properties
371
// which might otherwise be violated.  When adding code, people add according to the
372
// patterns they see.
373
//
374
// (We, sadly, don't have a good way to *ensure* use of `impl_standard_builder`.)
375
#[macro_export]
376
macro_rules! impl_standard_builder {
377
    // Convert the input into the "being processed format":
378
    {
379
        $Config:ty $(: $($options:tt)* )?
380
    } => { $crate::impl_standard_builder!{
381
        // ^Being processed format:
382
        @ ( Builder                    )
383
          ( default                    )
384
          ( extract                    ) $Config    :                 $( $( $options    )* )?
385
        //  ~~~~~~~~~~~~~~~              ^^^^^^^    ^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
386
        // present iff not !Builder, !Default
387
        // present iff not !Default
388
        // present iff not !Deserialize  type      always present    options yet to be parsed
389
    } };
390
    // If !Deserialize is the next option, implement it by making $try_deserialize absent
391
    {
392
        @ ( $($Builder        :ident)? )
393
          ( $($default        :ident)? )
394
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
395
    } => {  $crate::impl_standard_builder!{
396
        @ ( $($Builder              )? )
397
          ( $($default              )? )
398
          (                            ) $Config    :                    $( $options    )*
399
    } };
400
    // If !Builder is the next option, implement it by making $Builder absent
401
    {
402
        @ ( $($Builder        :ident)? )
403
          ( $($default        :ident)? )
404
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder     $( $options:tt )*
405
    } => {  $crate::impl_standard_builder!{
406
        @ (                            )
407
          ( $($default              )? )
408
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
409
    } };
410
    // If !Default is the next option, implement it by making $default absent
411
    {
412
        @ ( $($Builder        :ident)? )
413
          ( $($default        :ident)? )
414
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default     $( $options:tt )*
415
    } => {  $crate::impl_standard_builder!{
416
        @ ( $($Builder              )? )
417
          (                            )
418
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
419
    } };
420
    // Having parsed all options, produce output:
421
    {
422
        @ ( $($Builder        :ident)? )
423
          ( $($default        :ident)? )
424
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)?
425
    } => { $crate::deps::paste!{
426
        impl $Config {
427
            /// Returns a fresh, default, builder
428
572002
            pub fn builder() -> [< $Config Builder >] {
429
572002
                Default::default()
430
572002
            }
431
        }
432

            
433
        $( // expands iff there was $default, which is always default
434
            impl Default for $Config {
435
2204
                fn $default() -> Self {
436
2204
                    // unwrap is good because one of the test cases above checks that it works!
437
2204
                    [< $Config Builder >]::default().build().unwrap()
438
2204
                }
439
            }
440
        )?
441

            
442
        $( // expands iff there was $Builder, which is always Builder
443
            impl $crate::load::$Builder for [< $Config Builder >] {
444
                type Built = $Config;
445
632
                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
446
632
                    [< $Config Builder >]::build(self)
447
632
                }
448
            }
449
        )?
450

            
451
        #[test]
452
        #[allow(non_snake_case)]
453
76
        fn [< test_impl_Default_for_ $Config >] () {
454
76
            #[allow(unused_variables)]
455
76
            let def = None::<$Config>;
456
56
            $( // expands iff there was $default, which is always default
457
56
                let def = Some($Config::$default());
458
            )?
459

            
460
90
            if let Some(def) = def {
461
2
                $( // expands iff there was $try_deserialize, which is always extract
462
56
                    let empty_config = $crate::deps::figment::Figment::new();
463
55
                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
464
55
                    let from_empty = builder.build().unwrap();
465
55
                    assert_eq!(def, from_empty);
466
2
                )*
467
35
            }
468
6
        }
469
    } };
470
}
471

            
472
#[cfg(test)]
473
mod test {
474
    // @@ begin test lint list maintained by maint/add_warning @@
475
    #![allow(clippy::bool_assert_comparison)]
476
    #![allow(clippy::clone_on_copy)]
477
    #![allow(clippy::dbg_macro)]
478
    #![allow(clippy::mixed_attributes_style)]
479
    #![allow(clippy::print_stderr)]
480
    #![allow(clippy::print_stdout)]
481
    #![allow(clippy::single_char_pattern)]
482
    #![allow(clippy::unwrap_used)]
483
    #![allow(clippy::unchecked_duration_subtraction)]
484
    #![allow(clippy::useless_vec)]
485
    #![allow(clippy::needless_pass_by_value)]
486
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
487
    use super::*;
488
    use crate as tor_config;
489
    use derive_builder::Builder;
490
    use serde::{Deserialize, Serialize};
491
    use serde_json::json;
492
    use tracing_test::traced_test;
493

            
494
    #[test]
495
    #[traced_test]
496
    fn reconfigure_helpers() {
497
        let how = Reconfigure::AllOrNothing;
498
        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
499
        assert_eq!(
500
            err.to_string(),
501
            "Cannot change the_laws_of_physics on a running client.".to_owned()
502
        );
503

            
504
        let how = Reconfigure::WarnOnFailures;
505
        let ok = how.cannot_change("stuff");
506
        assert!(ok.is_ok());
507
        assert!(logs_contain("Cannot change stuff on a running client."));
508
    }
509

            
510
    #[test]
511
    #[rustfmt::skip] // autoformatting obscures the regular structure
512
    fn resolve_option_test() {
513
        #[derive(Debug, Clone, Builder, Eq, PartialEq)]
514
        #[builder(build_fn(error = "ConfigBuildError"))]
515
        #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
516
        struct TestConfig {
517
            #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
518
            none: Option<u32>,
519

            
520
            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
521
            four: Option<u32>,
522
        }
523

            
524
        // defaults
525
        {
526
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
527
                json!{ { } }
528
            ).unwrap();
529

            
530
            let builder_from_methods = TestConfigBuilder::default();
531

            
532
            assert_eq!(builder_from_methods, builder_from_json);
533
            assert_eq!(builder_from_methods.build().unwrap(),
534
                        TestConfig { none: None, four: Some(4) });
535
        }
536

            
537
        // explicit positive values
538
        {
539
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
540
                json!{ { "none": 123, "four": 456 } }
541
            ).unwrap();
542

            
543
            let mut builder_from_methods = TestConfigBuilder::default();
544
            builder_from_methods.none(Some(123));
545
            builder_from_methods.four(Some(456));
546

            
547
            assert_eq!(builder_from_methods, builder_from_json);
548
            assert_eq!(builder_from_methods.build().unwrap(),
549
                       TestConfig { none: Some(123), four: Some(456) });
550
        }
551

            
552
        // explicit "null" values
553
        {
554
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
555
                json!{ { "none": 0, "four": 0 } }
556
            ).unwrap();
557

            
558
            let mut builder_from_methods = TestConfigBuilder::default();
559
            builder_from_methods.none(Some(0));
560
            builder_from_methods.four(Some(0));
561

            
562
            assert_eq!(builder_from_methods, builder_from_json);
563
            assert_eq!(builder_from_methods.build().unwrap(),
564
                       TestConfig { none: None, four: None });
565
        }
566

            
567
        // explicit None (API only, serde can't do this for Option)
568
        {
569
            let mut builder_from_methods = TestConfigBuilder::default();
570
            builder_from_methods.none(None);
571
            builder_from_methods.four(None);
572

            
573
            assert_eq!(builder_from_methods.build().unwrap(),
574
                       TestConfig { none: None, four: None });
575
        }
576
    }
577
}