git_checks/
invalid_paths.rs

1// Copyright Kitware, Inc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11
12/// A check which denies commits which adds files containing special characters in their paths.
13#[derive(Builder, Debug, Default, Clone)]
14#[builder(field(private))]
15pub struct InvalidPaths {
16    /// Characters not allowed within a filename.
17    ///
18    /// In addition to whitespace, control, and non-ASCII characters, which are always disallowed.
19    ///
20    /// Configuration: Optional
21    /// Default: `String::new()
22    #[builder(setter(into))]
23    #[builder(default)]
24    invalid_characters: String,
25    /// Allow the space character (ASCII x20)
26    ///
27    /// This allows for exempting ` ` in paths.
28    ///
29    /// Configuration: Optional
30    /// Default: `false`
31    #[builder(default = "false")]
32    allow_space: bool,
33    /// Enforce Windows-specific path rules.
34    ///
35    /// This includes a list of reserved names as well as rejecting path components which end in
36    /// `.` or ` ` (space).
37    ///
38    /// Configuration: Optional
39    /// Default: `false`
40    #[builder(default = "false")]
41    enforce_windows_rules: bool,
42}
43
44const WINDOWS_FORBIDDEN_ASCII: &[u8] = br#"<>:"\|?*"#;
45const WINDOWS_FORBIDDEN_NAMES: &[&[u8]] = &[
46    b"CON", b"PRN", b"AUX", b"NUL", b"COM1", b"COM2", b"COM3", b"COM4", b"COM5", b"COM6", b"COM7",
47    b"COM8", b"COM9", b"LPT1", b"LPT2", b"LPT3", b"LPT4", b"LPT5", b"LPT6", b"LPT7", b"LPT8",
48    b"LPT9",
49];
50
51impl InvalidPaths {
52    /// Create a new builder.
53    pub fn builder() -> InvalidPathsBuilder {
54        Default::default()
55    }
56
57    fn disallowed_whitespace(&self, c: char) -> bool {
58        c.is_whitespace() && (!self.allow_space || c != ' ')
59    }
60
61    /// Whether a string has any invalid characters.
62    fn has_invalid_chars(&self, chars: &str) -> bool {
63        chars.chars().any(|ref c| {
64            !c.is_ascii()
65                || self.disallowed_whitespace(*c)
66                || c.is_control()
67                || self.invalid_characters.contains(*c)
68        })
69    }
70
71    /// Whether the string has any invalid bytes.
72    fn has_invalid_bytes(&self, bytes: &[u8]) -> bool {
73        bytes.iter().any(|&c| !c.is_ascii() || c < 32)
74    }
75
76    fn has_base_name(name: &[u8], base: &[u8]) -> bool {
77        name.starts_with(base) &&
78            // This is always in-bounds, because we already check if it *is* one of the names
79            // previously. That means that if it starts with `base`, it has at least one more byte
80            // to compare.
81            name[base.len()] == b'.'
82    }
83
84    /// Whether the string has any invalid bytes.
85    ///
86    /// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file for rules.
87    fn is_valid_for_windows(chars: &[u8]) -> bool {
88        // Check for invalid characters.
89        !chars.iter().any(|c| WINDOWS_FORBIDDEN_ASCII.contains(c)) &&
90            // Check component names.
91            !chars.split(|&b| b == b'/')
92                .any(|name| {
93                    name.ends_with(b" ") ||
94                        name.ends_with(b".") ||
95                        WINDOWS_FORBIDDEN_NAMES.contains(&name) ||
96                        WINDOWS_FORBIDDEN_NAMES.iter().any(|forbidden| Self::has_base_name(name, forbidden))
97                })
98    }
99}
100
101impl ContentCheck for InvalidPaths {
102    fn name(&self) -> &str {
103        "invalid-paths"
104    }
105
106    fn check(
107        &self,
108        _: &CheckGitContext,
109        content: &dyn Content,
110    ) -> Result<CheckResult, Box<dyn Error>> {
111        let mut result = CheckResult::new();
112
113        for diff in content.diffs() {
114            if let StatusChange::Added = diff.status {
115                let have_bad_char = self.has_invalid_chars(diff.name.as_str());
116                let have_bad_bytes = self.has_invalid_bytes(diff.name.as_bytes());
117
118                if have_bad_char || have_bad_bytes {
119                    result.add_error(format!(
120                        "{}adds the `{}` path which contains at least one forbidden character: \
121                         `<non-ASCII><whitespace><control>{}`.",
122                        commit_prefix(content),
123                        diff.name,
124                        self.invalid_characters,
125                    ));
126                }
127
128                if self.enforce_windows_rules && !Self::is_valid_for_windows(diff.name.as_bytes()) {
129                    result.add_error(format!(
130                        "{}adds the `{}` path which is invalid on Windows.",
131                        commit_prefix(content),
132                        diff.name,
133                    ));
134                }
135            }
136        }
137
138        Ok(result)
139    }
140}
141
142#[cfg(feature = "config")]
143pub(crate) mod config {
144    use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
145    use serde::Deserialize;
146    #[cfg(test)]
147    use serde_json::json;
148
149    use crate::InvalidPaths;
150
151    /// Configuration for the `InvalidPaths` check.
152    ///
153    /// The `invalid_characters` key is a string containing characters which are not allowed to
154    /// appear in paths. By default, its value is `"<>:\"|?*"` which excludes characters not
155    /// allowed in paths on Windows. The `allow_space` key is a boolean defaulting to false which
156    /// specifies whether ASCII space is allowed (whitespace is otherwise disallowed).
157    ///
158    /// This check is registered as a commit check with the name `"invalid_paths"` and a topic
159    /// check with the name `"invalid_paths/topic"`.
160    ///
161    /// # Example
162    ///
163    /// ```json
164    /// {
165    ///     "invalid_characters": "<>:\"|?*",
166    ///     "allow_space": false
167    /// }
168    /// ```
169    #[derive(Deserialize, Debug)]
170    pub struct InvalidPathsConfig {
171        #[serde(default)]
172        invalid_characters: Option<String>,
173        #[serde(default)]
174        allow_space: Option<bool>,
175        #[serde(default)]
176        enforce_windows_rules: Option<bool>,
177    }
178
179    impl IntoCheck for InvalidPathsConfig {
180        type Check = InvalidPaths;
181
182        fn into_check(self) -> Self::Check {
183            let mut builder = InvalidPaths::builder();
184
185            if let Some(invalid_characters) = self.invalid_characters {
186                builder.invalid_characters(invalid_characters);
187            }
188
189            if let Some(allow_space) = self.allow_space {
190                builder.allow_space(allow_space);
191            }
192
193            if let Some(enforce_windows_rules) = self.enforce_windows_rules {
194                builder.enforce_windows_rules(enforce_windows_rules);
195            }
196
197            builder
198                .build()
199                .expect("configuration mismatch for `InvalidPaths`")
200        }
201    }
202
203    register_checks! {
204        InvalidPathsConfig {
205            "invalid_paths" => CommitCheckConfig,
206            "invalid_paths/topic" => TopicCheckConfig,
207        },
208    }
209
210    #[test]
211    fn test_invalid_paths_config_empty() {
212        let json = json!({});
213        let check: InvalidPathsConfig = serde_json::from_value(json).unwrap();
214
215        assert_eq!(check.invalid_characters, None);
216        assert_eq!(check.allow_space, None);
217        assert_eq!(check.enforce_windows_rules, None);
218
219        let check = check.into_check();
220
221        assert_eq!(check.invalid_characters, "");
222        assert!(!check.allow_space);
223        assert!(!check.enforce_windows_rules);
224    }
225
226    #[test]
227    fn test_invalid_paths_config_all_fields() {
228        let json = json!({
229            "invalid_characters": "abc",
230            "allow_space": true,
231            "enforce_windows_rules": true,
232        });
233        let check: InvalidPathsConfig = serde_json::from_value(json).unwrap();
234
235        assert_eq!(check.invalid_characters, Some("abc".into()));
236        assert_eq!(check.allow_space, Some(true));
237        assert_eq!(check.enforce_windows_rules, Some(true));
238
239        let check = check.into_check();
240
241        assert_eq!(check.invalid_characters, "abc");
242        assert!(check.allow_space);
243        assert!(check.enforce_windows_rules);
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use git_checks_core::{Check, TopicCheck};
250
251    use crate::test::*;
252    use crate::InvalidPaths;
253
254    const BAD_TOPIC: &str = "f536f44cf96b82e479d4973d5ea1cf78058bd1fb";
255    const FIX_TOPIC: &str = "8ff69e1834ef2e82c0ed5cfb4ba56f1e4de85d03";
256    const BAD_WINDOWS_TOPIC: &str = "6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5";
257    const FIX_WINDOWS_TOPIC: &str = "e2130533af8b9c274c82ad8cdede5fdbb8053c7b";
258    const BAD_WINDOWS_BASENAMES_TOPIC: &str = "d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894";
259    const FIX_WINDOWS_BASENAMES_TOPIC: &str = "98c9e3ceb8c38dcf902e2868ea6e656dbbc02823";
260
261    #[test]
262    fn test_allow_space() {
263        let mut builder = InvalidPaths::builder();
264        let check = builder.build().unwrap();
265        assert!(!check.disallowed_whitespace('a'));
266        assert!(check.disallowed_whitespace(' '));
267        assert!(check.disallowed_whitespace('\n'));
268        builder.allow_space(true);
269        let check = builder.build().unwrap();
270        assert!(!check.disallowed_whitespace('a'));
271        assert!(!check.disallowed_whitespace(' '));
272        assert!(check.disallowed_whitespace('\n'));
273    }
274
275    #[test]
276    fn test_valid_windows_characters() {
277        assert!(!InvalidPaths::is_valid_for_windows(b"with<langle"));
278        assert!(!InvalidPaths::is_valid_for_windows(b"with>rangle"));
279        assert!(!InvalidPaths::is_valid_for_windows(b"with>rangle"));
280        assert!(!InvalidPaths::is_valid_for_windows(b"with:colon"));
281        assert!(!InvalidPaths::is_valid_for_windows(b"with\"dquote"));
282        assert!(!InvalidPaths::is_valid_for_windows(b"with\\slash"));
283        assert!(!InvalidPaths::is_valid_for_windows(b"with|vbar"));
284        assert!(!InvalidPaths::is_valid_for_windows(b"with?question"));
285        assert!(!InvalidPaths::is_valid_for_windows(b"with*asterisk"));
286    }
287
288    #[test]
289    fn test_valid_windows_suffixes() {
290        assert!(!InvalidPaths::is_valid_for_windows(b"ends.with."));
291        assert!(!InvalidPaths::is_valid_for_windows(b"ends.with "));
292        assert!(!InvalidPaths::is_valid_for_windows(b"dir.ends.with./file"));
293        assert!(!InvalidPaths::is_valid_for_windows(b"dir.ends.with /file"));
294    }
295
296    #[test]
297    fn test_valid_windows_reserved_names_as_files() {
298        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/CON"));
299        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/PRN"));
300        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/AUX"));
301        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/NUL"));
302        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM1"));
303        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM2"));
304        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM3"));
305        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM4"));
306        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM5"));
307        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM6"));
308        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM7"));
309        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM8"));
310        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/COM9"));
311        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT1"));
312        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT2"));
313        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT3"));
314        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT4"));
315        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT5"));
316        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT6"));
317        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT7"));
318        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT8"));
319        assert!(!InvalidPaths::is_valid_for_windows(b"forbidden/name/LPT9"));
320    }
321
322    #[test]
323    fn test_valid_windows_reserved_names_as_dirs() {
324        assert!(!InvalidPaths::is_valid_for_windows(
325            b"forbidden/name/CON/as/dir",
326        ));
327        assert!(!InvalidPaths::is_valid_for_windows(
328            b"forbidden/name/PRN/as/dir",
329        ));
330        assert!(!InvalidPaths::is_valid_for_windows(
331            b"forbidden/name/AUX/as/dir",
332        ));
333        assert!(!InvalidPaths::is_valid_for_windows(
334            b"forbidden/name/NUL/as/dir",
335        ));
336        assert!(!InvalidPaths::is_valid_for_windows(
337            b"forbidden/name/COM1/as/dir",
338        ));
339        assert!(!InvalidPaths::is_valid_for_windows(
340            b"forbidden/name/COM2/as/dir",
341        ));
342        assert!(!InvalidPaths::is_valid_for_windows(
343            b"forbidden/name/COM3/as/dir",
344        ));
345        assert!(!InvalidPaths::is_valid_for_windows(
346            b"forbidden/name/COM4/as/dir",
347        ));
348        assert!(!InvalidPaths::is_valid_for_windows(
349            b"forbidden/name/COM5/as/dir",
350        ));
351        assert!(!InvalidPaths::is_valid_for_windows(
352            b"forbidden/name/COM6/as/dir",
353        ));
354        assert!(!InvalidPaths::is_valid_for_windows(
355            b"forbidden/name/COM7/as/dir",
356        ));
357        assert!(!InvalidPaths::is_valid_for_windows(
358            b"forbidden/name/COM8/as/dir",
359        ));
360        assert!(!InvalidPaths::is_valid_for_windows(
361            b"forbidden/name/COM9/as/dir",
362        ));
363        assert!(!InvalidPaths::is_valid_for_windows(
364            b"forbidden/name/LPT1/as/dir",
365        ));
366        assert!(!InvalidPaths::is_valid_for_windows(
367            b"forbidden/name/LPT2/as/dir",
368        ));
369        assert!(!InvalidPaths::is_valid_for_windows(
370            b"forbidden/name/LPT3/as/dir",
371        ));
372        assert!(!InvalidPaths::is_valid_for_windows(
373            b"forbidden/name/LPT4/as/dir",
374        ));
375        assert!(!InvalidPaths::is_valid_for_windows(
376            b"forbidden/name/LPT5/as/dir",
377        ));
378        assert!(!InvalidPaths::is_valid_for_windows(
379            b"forbidden/name/LPT6/as/dir",
380        ));
381        assert!(!InvalidPaths::is_valid_for_windows(
382            b"forbidden/name/LPT7/as/dir",
383        ));
384        assert!(!InvalidPaths::is_valid_for_windows(
385            b"forbidden/name/LPT8/as/dir",
386        ));
387        assert!(!InvalidPaths::is_valid_for_windows(
388            b"forbidden/name/LPT9/as/dir",
389        ));
390    }
391
392    #[test]
393    fn test_invalid_paths_builder_default() {
394        assert!(InvalidPaths::builder().build().is_ok());
395    }
396
397    #[test]
398    fn test_invalid_paths_name_commit() {
399        let check = InvalidPaths::default();
400        assert_eq!(Check::name(&check), "invalid-paths");
401    }
402
403    #[test]
404    fn test_invalid_paths_name_topic() {
405        let check = InvalidPaths::default();
406        assert_eq!(TopicCheck::name(&check), "invalid-paths");
407    }
408
409    #[test]
410    fn test_invalid_paths() {
411        let check = InvalidPaths::builder()
412            .invalid_characters("$")
413            .build()
414            .unwrap();
415        let result = run_check("test_invalid_paths", BAD_TOPIC, check);
416        test_result_errors(result, &[
417            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
418             `\"control-character-\\003\"` path which contains at least one forbidden character: \
419             `<non-ASCII><whitespace><control>$`.",
420            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
421             `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
422             character: `<non-ASCII><whitespace><control>$`.",
423            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
424             `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
425             character: `<non-ASCII><whitespace><control>$`.",
426            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with whitespace` \
427             path which contains at least one forbidden character: \
428             `<non-ASCII><whitespace><control>$`.",
429            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with-dollar-$` path \
430             which contains at least one forbidden character: \
431             `<non-ASCII><whitespace><control>$`.",
432        ]);
433    }
434
435    #[test]
436    fn test_invalid_paths_allow_space() {
437        let check = InvalidPaths::builder()
438            .invalid_characters("$")
439            .allow_space(true)
440            .build()
441            .unwrap();
442        let result = run_check("test_invalid_paths_allow_space", BAD_TOPIC, check);
443        test_result_errors(result, &[
444            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
445             `\"control-character-\\003\"` path which contains at least one forbidden character: \
446             `<non-ASCII><whitespace><control>$`.",
447            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
448             `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
449             character: `<non-ASCII><whitespace><control>$`.",
450            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
451             `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
452             character: `<non-ASCII><whitespace><control>$`.",
453            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with-dollar-$` path \
454             which contains at least one forbidden character: \
455             `<non-ASCII><whitespace><control>$`.",
456        ]);
457    }
458
459    #[test]
460    fn test_invalid_paths_topic() {
461        let check = InvalidPaths::builder()
462            .invalid_characters("$")
463            .build()
464            .unwrap();
465        let result = run_topic_check("test_invalid_paths_topic", BAD_TOPIC, check);
466        test_result_errors(result, &[
467            "adds the `\"control-character-\\003\"` path which contains at least one forbidden \
468             character: `<non-ASCII><whitespace><control>$`.",
469            "adds the `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
470             character: `<non-ASCII><whitespace><control>$`.",
471            "adds the `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
472             character: `<non-ASCII><whitespace><control>$`.",
473            "adds the `with whitespace` path which contains at least one forbidden character: \
474             `<non-ASCII><whitespace><control>$`.",
475            "adds the `with-dollar-$` path which contains at least one forbidden character: \
476             `<non-ASCII><whitespace><control>$`.",
477        ]);
478    }
479
480    #[test]
481    fn test_invalid_paths_topic_allow_space() {
482        let check = InvalidPaths::builder()
483            .invalid_characters("$")
484            .allow_space(true)
485            .build()
486            .unwrap();
487        let result = run_topic_check("test_invalid_paths_topic_allow_space", BAD_TOPIC, check);
488        test_result_errors(result, &[
489            "adds the `\"control-character-\\003\"` path which contains at least one forbidden \
490             character: `<non-ASCII><whitespace><control>$`.",
491            "adds the `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
492             character: `<non-ASCII><whitespace><control>$`.",
493            "adds the `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
494             character: `<non-ASCII><whitespace><control>$`.",
495            "adds the `with-dollar-$` path which contains at least one forbidden character: \
496             `<non-ASCII><whitespace><control>$`.",
497        ]);
498    }
499
500    #[test]
501    fn test_invalid_paths_default() {
502        let check = InvalidPaths::default();
503        let result = run_check("test_invalid_paths_default", BAD_TOPIC, check);
504        test_result_errors(result, &[
505            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
506             `\"control-character-\\003\"` path which contains at least one forbidden character: \
507             `<non-ASCII><whitespace><control>`.",
508            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"invalid-utf8-\\200\"` \
509             path which contains at least one forbidden character: \
510             `<non-ASCII><whitespace><control>`.",
511            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"non-ascii-\\303\\251\"` \
512             path which contains at least one forbidden character: \
513             `<non-ASCII><whitespace><control>`.",
514            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with whitespace` path \
515             which contains at least one forbidden character: \
516             `<non-ASCII><whitespace><control>`.",
517        ]);
518    }
519
520    #[test]
521    fn test_invalid_paths_default_allow_space() {
522        let check = InvalidPaths::builder().allow_space(true).build().unwrap();
523        let result = run_check("test_invalid_paths_default_allow_space", BAD_TOPIC, check);
524        test_result_errors(result, &[
525            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
526             `\"control-character-\\003\"` path which contains at least one forbidden character: \
527             `<non-ASCII><whitespace><control>`.",
528            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"invalid-utf8-\\200\"` \
529             path which contains at least one forbidden character: \
530             `<non-ASCII><whitespace><control>`.",
531            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"non-ascii-\\303\\251\"` \
532             path which contains at least one forbidden character: \
533             `<non-ASCII><whitespace><control>`.",
534        ]);
535    }
536
537    #[test]
538    fn test_invalid_paths_topic_fixed() {
539        let check = InvalidPaths::default();
540        run_topic_check_ok("test_invalid_paths_topic_fixed", FIX_TOPIC, check);
541    }
542
543    #[test]
544    fn test_invalid_paths_topic_windows_ignore() {
545        let check = InvalidPaths::builder()
546            // Ignore the space in the path name testing path suffixes.
547            .allow_space(true)
548            .build()
549            .unwrap();
550        run_topic_check_ok(
551            "test_invalid_paths_topic_windows_ignore",
552            BAD_WINDOWS_TOPIC,
553            check,
554        );
555    }
556
557    #[test]
558    fn test_invalid_paths_windows() {
559        let check = InvalidPaths::builder()
560            .allow_space(true)
561            .enforce_windows_rules(true)
562            .build()
563            .unwrap();
564        let result = run_check("test_invalid_paths_windows", BAD_WINDOWS_TOPIC, check);
565        test_result_errors(
566            result,
567            &[
568                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `"invalid-char-\"-dir/subdir"` path which is invalid on Windows."#,
569                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `"invalid-char-\"-file"` path which is invalid on Windows."#,
570                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-*-dir/subdir` path which is invalid on Windows."#,
571                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-*-file` path which is invalid on Windows."#,
572                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-:-dir/subdir` path which is invalid on Windows."#,
573                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-:-file` path which is invalid on Windows."#,
574                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-<-dir/subdir` path which is invalid on Windows."#,
575                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-<-file` path which is invalid on Windows."#,
576                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char->-dir/subdir` path which is invalid on Windows."#,
577                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char->-file` path which is invalid on Windows."#,
578                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-?-dir/subdir` path which is invalid on Windows."#,
579                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-char-?-file` path which is invalid on Windows."#,
580                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `"invalid-char-\\-dir/subdir"` path which is invalid on Windows."#,
581                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `"invalid-char-\\-file"` path which is invalid on Windows."#,
582                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/AUX/subdir` path which is invalid on Windows."#,
583                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM1/subdir` path which is invalid on Windows."#,
584                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM2/subdir` path which is invalid on Windows."#,
585                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM3/subdir` path which is invalid on Windows."#,
586                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM4/subdir` path which is invalid on Windows."#,
587                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM5/subdir` path which is invalid on Windows."#,
588                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM6/subdir` path which is invalid on Windows."#,
589                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM7/subdir` path which is invalid on Windows."#,
590                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM8/subdir` path which is invalid on Windows."#,
591                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/COM9/subdir` path which is invalid on Windows."#,
592                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/CON/subdir` path which is invalid on Windows."#,
593                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT1/subdir` path which is invalid on Windows."#,
594                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT2/subdir` path which is invalid on Windows."#,
595                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT3/subdir` path which is invalid on Windows."#,
596                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT4/subdir` path which is invalid on Windows."#,
597                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT5/subdir` path which is invalid on Windows."#,
598                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT6/subdir` path which is invalid on Windows."#,
599                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT7/subdir` path which is invalid on Windows."#,
600                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT8/subdir` path which is invalid on Windows."#,
601                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/LPT9/subdir` path which is invalid on Windows."#,
602                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/NUL/subdir` path which is invalid on Windows."#,
603                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/dirs/PRN/subdir` path which is invalid on Windows."#,
604                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/AUX` path which is invalid on Windows."#,
605                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM1` path which is invalid on Windows."#,
606                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM2` path which is invalid on Windows."#,
607                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM3` path which is invalid on Windows."#,
608                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM4` path which is invalid on Windows."#,
609                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM5` path which is invalid on Windows."#,
610                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM6` path which is invalid on Windows."#,
611                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM7` path which is invalid on Windows."#,
612                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM8` path which is invalid on Windows."#,
613                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/COM9` path which is invalid on Windows."#,
614                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/CON` path which is invalid on Windows."#,
615                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT1` path which is invalid on Windows."#,
616                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT2` path which is invalid on Windows."#,
617                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT3` path which is invalid on Windows."#,
618                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT4` path which is invalid on Windows."#,
619                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT5` path which is invalid on Windows."#,
620                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT6` path which is invalid on Windows."#,
621                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT7` path which is invalid on Windows."#,
622                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT8` path which is invalid on Windows."#,
623                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/LPT9` path which is invalid on Windows."#,
624                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/NUL` path which is invalid on Windows."#,
625                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-names/files/PRN` path which is invalid on Windows."#,
626                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-suffix-dir- /subdir` path which is invalid on Windows."#,
627                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-suffix-dir-./subdir` path which is invalid on Windows."#,
628                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-suffix-file- ` path which is invalid on Windows."#,
629                r#"commit 6354b8d1b1c247d0e35faf2de3ab1be0b7e582a5 adds the `invalid-suffix-file-.` path which is invalid on Windows."#,
630            ],
631        );
632    }
633
634    #[test]
635    fn test_invalid_paths_windows_topic() {
636        let check = InvalidPaths::builder()
637            .allow_space(true)
638            .enforce_windows_rules(true)
639            .build()
640            .unwrap();
641        let result = run_topic_check("test_invalid_paths_windows_topic", BAD_WINDOWS_TOPIC, check);
642        test_result_errors(
643            result,
644            &[
645                r#"adds the `"invalid-char-\"-dir/subdir"` path which is invalid on Windows."#,
646                r#"adds the `"invalid-char-\"-file"` path which is invalid on Windows."#,
647                r#"adds the `invalid-char-*-dir/subdir` path which is invalid on Windows."#,
648                r#"adds the `invalid-char-*-file` path which is invalid on Windows."#,
649                r#"adds the `invalid-char-:-dir/subdir` path which is invalid on Windows."#,
650                r#"adds the `invalid-char-:-file` path which is invalid on Windows."#,
651                r#"adds the `invalid-char-<-dir/subdir` path which is invalid on Windows."#,
652                r#"adds the `invalid-char-<-file` path which is invalid on Windows."#,
653                r#"adds the `invalid-char->-dir/subdir` path which is invalid on Windows."#,
654                r#"adds the `invalid-char->-file` path which is invalid on Windows."#,
655                r#"adds the `invalid-char-?-dir/subdir` path which is invalid on Windows."#,
656                r#"adds the `invalid-char-?-file` path which is invalid on Windows."#,
657                r#"adds the `"invalid-char-\\-dir/subdir"` path which is invalid on Windows."#,
658                r#"adds the `"invalid-char-\\-file"` path which is invalid on Windows."#,
659                r#"adds the `invalid-names/dirs/AUX/subdir` path which is invalid on Windows."#,
660                r#"adds the `invalid-names/dirs/COM1/subdir` path which is invalid on Windows."#,
661                r#"adds the `invalid-names/dirs/COM2/subdir` path which is invalid on Windows."#,
662                r#"adds the `invalid-names/dirs/COM3/subdir` path which is invalid on Windows."#,
663                r#"adds the `invalid-names/dirs/COM4/subdir` path which is invalid on Windows."#,
664                r#"adds the `invalid-names/dirs/COM5/subdir` path which is invalid on Windows."#,
665                r#"adds the `invalid-names/dirs/COM6/subdir` path which is invalid on Windows."#,
666                r#"adds the `invalid-names/dirs/COM7/subdir` path which is invalid on Windows."#,
667                r#"adds the `invalid-names/dirs/COM8/subdir` path which is invalid on Windows."#,
668                r#"adds the `invalid-names/dirs/COM9/subdir` path which is invalid on Windows."#,
669                r#"adds the `invalid-names/dirs/CON/subdir` path which is invalid on Windows."#,
670                r#"adds the `invalid-names/dirs/LPT1/subdir` path which is invalid on Windows."#,
671                r#"adds the `invalid-names/dirs/LPT2/subdir` path which is invalid on Windows."#,
672                r#"adds the `invalid-names/dirs/LPT3/subdir` path which is invalid on Windows."#,
673                r#"adds the `invalid-names/dirs/LPT4/subdir` path which is invalid on Windows."#,
674                r#"adds the `invalid-names/dirs/LPT5/subdir` path which is invalid on Windows."#,
675                r#"adds the `invalid-names/dirs/LPT6/subdir` path which is invalid on Windows."#,
676                r#"adds the `invalid-names/dirs/LPT7/subdir` path which is invalid on Windows."#,
677                r#"adds the `invalid-names/dirs/LPT8/subdir` path which is invalid on Windows."#,
678                r#"adds the `invalid-names/dirs/LPT9/subdir` path which is invalid on Windows."#,
679                r#"adds the `invalid-names/dirs/NUL/subdir` path which is invalid on Windows."#,
680                r#"adds the `invalid-names/dirs/PRN/subdir` path which is invalid on Windows."#,
681                r#"adds the `invalid-names/files/AUX` path which is invalid on Windows."#,
682                r#"adds the `invalid-names/files/COM1` path which is invalid on Windows."#,
683                r#"adds the `invalid-names/files/COM2` path which is invalid on Windows."#,
684                r#"adds the `invalid-names/files/COM3` path which is invalid on Windows."#,
685                r#"adds the `invalid-names/files/COM4` path which is invalid on Windows."#,
686                r#"adds the `invalid-names/files/COM5` path which is invalid on Windows."#,
687                r#"adds the `invalid-names/files/COM6` path which is invalid on Windows."#,
688                r#"adds the `invalid-names/files/COM7` path which is invalid on Windows."#,
689                r#"adds the `invalid-names/files/COM8` path which is invalid on Windows."#,
690                r#"adds the `invalid-names/files/COM9` path which is invalid on Windows."#,
691                r#"adds the `invalid-names/files/CON` path which is invalid on Windows."#,
692                r#"adds the `invalid-names/files/LPT1` path which is invalid on Windows."#,
693                r#"adds the `invalid-names/files/LPT2` path which is invalid on Windows."#,
694                r#"adds the `invalid-names/files/LPT3` path which is invalid on Windows."#,
695                r#"adds the `invalid-names/files/LPT4` path which is invalid on Windows."#,
696                r#"adds the `invalid-names/files/LPT5` path which is invalid on Windows."#,
697                r#"adds the `invalid-names/files/LPT6` path which is invalid on Windows."#,
698                r#"adds the `invalid-names/files/LPT7` path which is invalid on Windows."#,
699                r#"adds the `invalid-names/files/LPT8` path which is invalid on Windows."#,
700                r#"adds the `invalid-names/files/LPT9` path which is invalid on Windows."#,
701                r#"adds the `invalid-names/files/NUL` path which is invalid on Windows."#,
702                r#"adds the `invalid-names/files/PRN` path which is invalid on Windows."#,
703                r#"adds the `invalid-suffix-dir- /subdir` path which is invalid on Windows."#,
704                r#"adds the `invalid-suffix-dir-./subdir` path which is invalid on Windows."#,
705                r#"adds the `invalid-suffix-file- ` path which is invalid on Windows."#,
706                r#"adds the `invalid-suffix-file-.` path which is invalid on Windows."#,
707            ],
708        );
709    }
710
711    #[test]
712    fn test_invalid_paths_windows_topic_fixed() {
713        let check = InvalidPaths::builder()
714            .enforce_windows_rules(true)
715            .build()
716            .unwrap();
717        run_topic_check_ok(
718            "test_invalid_paths_windows_topic_fixed",
719            FIX_WINDOWS_TOPIC,
720            check,
721        );
722    }
723
724    #[test]
725    fn test_invalid_paths_windows_basenames() {
726        let check = InvalidPaths::builder()
727            .allow_space(true)
728            .enforce_windows_rules(true)
729            .build()
730            .unwrap();
731        let result = run_check(
732            "test_invalid_paths_windows_basenames",
733            BAD_WINDOWS_BASENAMES_TOPIC,
734            check,
735        );
736        test_result_errors(
737            result,
738            &[
739                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `AUX.dir/subdir` path which is invalid on Windows."#,
740                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `AUX.txt` path which is invalid on Windows."#,
741                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM1.dir/subdir` path which is invalid on Windows."#,
742                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM1.txt` path which is invalid on Windows."#,
743                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM2.dir/subdir` path which is invalid on Windows."#,
744                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM2.txt` path which is invalid on Windows."#,
745                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM3.dir/subdir` path which is invalid on Windows."#,
746                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM3.txt` path which is invalid on Windows."#,
747                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM4.dir/subdir` path which is invalid on Windows."#,
748                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM4.txt` path which is invalid on Windows."#,
749                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM5.dir/subdir` path which is invalid on Windows."#,
750                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM5.txt` path which is invalid on Windows."#,
751                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM6.dir/subdir` path which is invalid on Windows."#,
752                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM6.txt` path which is invalid on Windows."#,
753                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM7.dir/subdir` path which is invalid on Windows."#,
754                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM7.txt` path which is invalid on Windows."#,
755                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM8.dir/subdir` path which is invalid on Windows."#,
756                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM8.txt` path which is invalid on Windows."#,
757                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM9.dir/subdir` path which is invalid on Windows."#,
758                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `COM9.txt` path which is invalid on Windows."#,
759                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `CON.dir/subdir` path which is invalid on Windows."#,
760                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `CON.txt` path which is invalid on Windows."#,
761                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT1.dir/subdir` path which is invalid on Windows."#,
762                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT1.txt` path which is invalid on Windows."#,
763                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT2.dir/subdir` path which is invalid on Windows."#,
764                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT2.txt` path which is invalid on Windows."#,
765                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT3.dir/subdir` path which is invalid on Windows."#,
766                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT3.txt` path which is invalid on Windows."#,
767                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT4.dir/subdir` path which is invalid on Windows."#,
768                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT4.txt` path which is invalid on Windows."#,
769                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT5.dir/subdir` path which is invalid on Windows."#,
770                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT5.txt` path which is invalid on Windows."#,
771                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT6.dir/subdir` path which is invalid on Windows."#,
772                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT6.txt` path which is invalid on Windows."#,
773                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT7.dir/subdir` path which is invalid on Windows."#,
774                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT7.txt` path which is invalid on Windows."#,
775                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT8.dir/subdir` path which is invalid on Windows."#,
776                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT8.txt` path which is invalid on Windows."#,
777                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT9.dir/subdir` path which is invalid on Windows."#,
778                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `LPT9.txt` path which is invalid on Windows."#,
779                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `NUL.dir/subdir` path which is invalid on Windows."#,
780                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `NUL.txt` path which is invalid on Windows."#,
781                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `PRN.dir/subdir` path which is invalid on Windows."#,
782                r#"commit d9673d409ecb0c6647ad9ea0bd2fc3b7f314f894 adds the `PRN.txt` path which is invalid on Windows."#,
783            ],
784        );
785    }
786
787    #[test]
788    fn test_invalid_paths_windows_basenames_topic() {
789        let check = InvalidPaths::builder()
790            .allow_space(true)
791            .enforce_windows_rules(true)
792            .build()
793            .unwrap();
794        let result = run_topic_check(
795            "test_invalid_paths_windows_basenames_topic",
796            BAD_WINDOWS_BASENAMES_TOPIC,
797            check,
798        );
799        test_result_errors(
800            result,
801            &[
802                r#"adds the `AUX.dir/subdir` path which is invalid on Windows."#,
803                r#"adds the `AUX.txt` path which is invalid on Windows."#,
804                r#"adds the `COM1.dir/subdir` path which is invalid on Windows."#,
805                r#"adds the `COM1.txt` path which is invalid on Windows."#,
806                r#"adds the `COM2.dir/subdir` path which is invalid on Windows."#,
807                r#"adds the `COM2.txt` path which is invalid on Windows."#,
808                r#"adds the `COM3.dir/subdir` path which is invalid on Windows."#,
809                r#"adds the `COM3.txt` path which is invalid on Windows."#,
810                r#"adds the `COM4.dir/subdir` path which is invalid on Windows."#,
811                r#"adds the `COM4.txt` path which is invalid on Windows."#,
812                r#"adds the `COM5.dir/subdir` path which is invalid on Windows."#,
813                r#"adds the `COM5.txt` path which is invalid on Windows."#,
814                r#"adds the `COM6.dir/subdir` path which is invalid on Windows."#,
815                r#"adds the `COM6.txt` path which is invalid on Windows."#,
816                r#"adds the `COM7.dir/subdir` path which is invalid on Windows."#,
817                r#"adds the `COM7.txt` path which is invalid on Windows."#,
818                r#"adds the `COM8.dir/subdir` path which is invalid on Windows."#,
819                r#"adds the `COM8.txt` path which is invalid on Windows."#,
820                r#"adds the `COM9.dir/subdir` path which is invalid on Windows."#,
821                r#"adds the `COM9.txt` path which is invalid on Windows."#,
822                r#"adds the `CON.dir/subdir` path which is invalid on Windows."#,
823                r#"adds the `CON.txt` path which is invalid on Windows."#,
824                r#"adds the `LPT1.dir/subdir` path which is invalid on Windows."#,
825                r#"adds the `LPT1.txt` path which is invalid on Windows."#,
826                r#"adds the `LPT2.dir/subdir` path which is invalid on Windows."#,
827                r#"adds the `LPT2.txt` path which is invalid on Windows."#,
828                r#"adds the `LPT3.dir/subdir` path which is invalid on Windows."#,
829                r#"adds the `LPT3.txt` path which is invalid on Windows."#,
830                r#"adds the `LPT4.dir/subdir` path which is invalid on Windows."#,
831                r#"adds the `LPT4.txt` path which is invalid on Windows."#,
832                r#"adds the `LPT5.dir/subdir` path which is invalid on Windows."#,
833                r#"adds the `LPT5.txt` path which is invalid on Windows."#,
834                r#"adds the `LPT6.dir/subdir` path which is invalid on Windows."#,
835                r#"adds the `LPT6.txt` path which is invalid on Windows."#,
836                r#"adds the `LPT7.dir/subdir` path which is invalid on Windows."#,
837                r#"adds the `LPT7.txt` path which is invalid on Windows."#,
838                r#"adds the `LPT8.dir/subdir` path which is invalid on Windows."#,
839                r#"adds the `LPT8.txt` path which is invalid on Windows."#,
840                r#"adds the `LPT9.dir/subdir` path which is invalid on Windows."#,
841                r#"adds the `LPT9.txt` path which is invalid on Windows."#,
842                r#"adds the `NUL.dir/subdir` path which is invalid on Windows."#,
843                r#"adds the `NUL.txt` path which is invalid on Windows."#,
844                r#"adds the `PRN.dir/subdir` path which is invalid on Windows."#,
845                r#"adds the `PRN.txt` path which is invalid on Windows."#,
846            ],
847        );
848    }
849
850    #[test]
851    fn test_invalid_paths_windows_topic_basenames_fixed() {
852        let check = InvalidPaths::builder()
853            .enforce_windows_rules(true)
854            .build()
855            .unwrap();
856        run_topic_check_ok(
857            "test_invalid_paths_windows_topic_basenames_fixed",
858            FIX_WINDOWS_BASENAMES_TOPIC,
859            check,
860        );
861    }
862}