| @@ -1,204 +1,212 @@ |
| 1 | use git_attributes::State; | 1 | use git_attributes::State; |
| 2 | use git_pathspec::parse::Error; | | |
| 3 | use git_pathspec::{MagicSignature, Pattern, SearchMode}; | 2 | use git_pathspec::{MagicSignature, Pattern, SearchMode}; |
| 4 | | 3 | |
| 5 | #[test] | 4 | mod succeed { |
| 6 | fn can_parse_empty_signatures() { | 5 | use crate::{check_valid_inputs, pat, pat_with_path, pat_with_path_and_sig}; |
| 7 | let inputs = vec![ | 6 | use git_attributes::State; |
| 8 | ("some/path", pat_with_path("some/path")), | 7 | use git_pathspec::{MagicSignature, SearchMode}; |
| 9 | (":some/path", pat_with_path("some/path")), | 8 | |
| 10 | (":()some/path", pat_with_path("some/path")), | 9 | #[test] |
| 11 | ("::some/path", pat_with_path("some/path")), | 10 | fn empty_signatures() { |
| 12 | (":::some/path", pat_with_path(":some/path")), | 11 | let inputs = vec![ |
| 13 | ]; | 12 | ("some/path", pat_with_path("some/path")), |
| 14 | | 13 | (":some/path", pat_with_path("some/path")), |
| 15 | check_valid_inputs(inputs) | 14 | (":()some/path", pat_with_path("some/path")), |
| 16 | } | 15 | ("::some/path", pat_with_path("some/path")), |
| | 16 | (":::some/path", pat_with_path(":some/path")), |
| | 17 | ]; |
| | 18 | |
| | 19 | check_valid_inputs(inputs) |
| | 20 | } |
| 17 | | 21 | |
| 18 | #[test] | 22 | #[test] |
| 19 | fn can_parse_short_signatures() { | 23 | fn short_signatures() { |
| 20 | let inputs = vec![ | 24 | let inputs = vec![ |
| 21 | (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), | 25 | (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), |
| 22 | ( | 26 | ( |
| 23 | ":^some/path", | 27 | ":^some/path", |
| 24 | pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), | 28 | pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), |
| 25 | ), | 29 | ), |
| 26 | ( | 30 | ( |
| 27 | ":!some/path", | 31 | ":!some/path", |
| 28 | pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), | 32 | pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), |
| 29 | ), | 33 | ), |
| 30 | ( | 34 | ( |
| 31 | ":/!some/path", | 35 | ":/!some/path", |
| 32 | pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), | 36 | pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), |
| 33 | ), | 37 | ), |
| 34 | ( | 38 | ( |
| 35 | ":!/^/:", | 39 | ":!/^/:", |
| 36 | pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), | 40 | pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), |
| 37 | ), | 41 | ), |
| 38 | ]; | 42 | ]; |
| 39 | | | |
| 40 | check_valid_inputs(inputs) | | |
| 41 | } | | |
| 42 | | 43 | |
| 43 | #[test] | 44 | check_valid_inputs(inputs) |
| 44 | fn can_parse_signatures_and_searchmodes() { | 45 | } |
| 45 | let inputs = vec![ | | |
| 46 | (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), | | |
| 47 | (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), | | |
| 48 | (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), | | |
| 49 | (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), | | |
| 50 | ( | | |
| 51 | ":(literal)", | | |
| 52 | pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), | | |
| 53 | ), | | |
| 54 | (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), | | |
| 55 | ( | | |
| 56 | ":(top,exclude)", | | |
| 57 | pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), | | |
| 58 | ), | | |
| 59 | ( | | |
| 60 | ":(icase,literal)", | | |
| 61 | pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), | | |
| 62 | ), | | |
| 63 | ( | | |
| 64 | ":!(literal)some/*path", | | |
| 65 | pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), | | |
| 66 | ), | | |
| 67 | ( | | |
| 68 | ":(top,literal,icase,attr,exclude)some/path", | | |
| 69 | pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), | | |
| 70 | ), | | |
| 71 | ( | | |
| 72 | ":(top,glob,icase,attr,exclude)some/path", | | |
| 73 | pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), | | |
| 74 | ), | | |
| 75 | ]; | | |
| 76 | | | |
| 77 | check_valid_inputs(inputs); | | |
| 78 | } | | |
| 79 | | 46 | |
| 80 | #[test] | 47 | #[test] |
| 81 | fn can_parse_attributes_in_signature() { | 48 | fn signatures_and_searchmodes() { |
| 82 | let inputs = vec![ | 49 | let inputs = vec![ |
| 83 | ( | 50 | (":(top)", pat_with_path_and_sig("", MagicSignature::TOP)), |
| 84 | ":(attr:someAttr)", | 51 | (":(icase)", pat_with_path_and_sig("", MagicSignature::ICASE)), |
| 85 | pat( | 52 | (":(attr)", pat_with_path_and_sig("", MagicSignature::ATTR)), |
| 86 | "", | 53 | (":(exclude)", pat_with_path_and_sig("", MagicSignature::EXCLUDE)), |
| 87 | MagicSignature::ATTR, | 54 | ( |
| 88 | SearchMode::Default, | 55 | ":(literal)", |
| 89 | vec![("someAttr", State::Set)], | 56 | pat("", MagicSignature::empty(), SearchMode::Literal, vec![]), |
| 90 | ), | 57 | ), |
| 91 | ), | 58 | (":(glob)", pat("", MagicSignature::empty(), SearchMode::Glob, vec![])), |
| 92 | ( | 59 | ( |
| 93 | ":(attr:!someAttr)", | 60 | ":(top,exclude)", |
| 94 | pat( | 61 | pat_with_path_and_sig("", MagicSignature::TOP | MagicSignature::EXCLUDE), |
| 95 | "", | | |
| 96 | MagicSignature::ATTR, | | |
| 97 | SearchMode::Default, | | |
| 98 | vec![("someAttr", State::Unspecified)], | | |
| 99 | ), | 62 | ), |
| 100 | ), | 63 | ( |
| 101 | ( | 64 | ":(icase,literal)", |
| 102 | ":(attr:-someAttr)", | 65 | pat("", MagicSignature::ICASE, SearchMode::Literal, vec![]), |
| 103 | pat( | | |
| 104 | "", | | |
| 105 | MagicSignature::ATTR, | | |
| 106 | SearchMode::Default, | | |
| 107 | vec![("someAttr", State::Unset)], | | |
| 108 | ), | 66 | ), |
| 109 | ), | 67 | ( |
| 110 | ( | 68 | ":!(literal)some/*path", |
| 111 | ":(attr:someAttr=value)", | 69 | pat("some/*path", MagicSignature::EXCLUDE, SearchMode::Literal, vec![]), |
| 112 | pat( | | |
| 113 | "", | | |
| 114 | MagicSignature::ATTR, | | |
| 115 | SearchMode::Default, | | |
| 116 | vec![("someAttr", State::Value("value".into()))], | | |
| 117 | ), | 70 | ), |
| 118 | ), | 71 | ( |
| 119 | ( | 72 | ":(top,literal,icase,attr,exclude)some/path", |
| 120 | ":(attr:someAttr anotherAttr)", | 73 | pat("some/path", MagicSignature::all(), SearchMode::Literal, vec![]), |
| 121 | pat( | | |
| 122 | "", | | |
| 123 | MagicSignature::ATTR, | | |
| 124 | SearchMode::Default, | | |
| 125 | vec![("someAttr", State::Set), ("anotherAttr", State::Set)], | | |
| 126 | ), | 74 | ), |
| 127 | ), | 75 | ( |
| 128 | ]; | 76 | ":(top,glob,icase,attr,exclude)some/path", |
| 129 | | 77 | pat("some/path", MagicSignature::all(), SearchMode::Glob, vec![]), |
| 130 | check_valid_inputs(inputs) | 78 | ), |
| 131 | } | 79 | ]; |
| 132 | | 80 | |
| 133 | #[test] | 81 | check_valid_inputs(inputs); |
| 134 | fn should_fail_on_empty_input() { | 82 | } |
| 135 | let input = ""; | | |
| 136 | | 83 | |
| 137 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); | 84 | #[test] |
| | 85 | fn attributes_in_signature() { |
| | 86 | let inputs = vec![ |
| | 87 | ( |
| | 88 | ":(attr:someAttr)", |
| | 89 | pat( |
| | 90 | "", |
| | 91 | MagicSignature::ATTR, |
| | 92 | SearchMode::Default, |
| | 93 | vec![("someAttr", State::Set)], |
| | 94 | ), |
| | 95 | ), |
| | 96 | ( |
| | 97 | ":(attr:!someAttr)", |
| | 98 | pat( |
| | 99 | "", |
| | 100 | MagicSignature::ATTR, |
| | 101 | SearchMode::Default, |
| | 102 | vec![("someAttr", State::Unspecified)], |
| | 103 | ), |
| | 104 | ), |
| | 105 | ( |
| | 106 | ":(attr:-someAttr)", |
| | 107 | pat( |
| | 108 | "", |
| | 109 | MagicSignature::ATTR, |
| | 110 | SearchMode::Default, |
| | 111 | vec![("someAttr", State::Unset)], |
| | 112 | ), |
| | 113 | ), |
| | 114 | ( |
| | 115 | ":(attr:someAttr=value)", |
| | 116 | pat( |
| | 117 | "", |
| | 118 | MagicSignature::ATTR, |
| | 119 | SearchMode::Default, |
| | 120 | vec![("someAttr", State::Value("value".into()))], |
| | 121 | ), |
| | 122 | ), |
| | 123 | ( |
| | 124 | ":(attr:someAttr anotherAttr)", |
| | 125 | pat( |
| | 126 | "", |
| | 127 | MagicSignature::ATTR, |
| | 128 | SearchMode::Default, |
| | 129 | vec![("someAttr", State::Set), ("anotherAttr", State::Set)], |
| | 130 | ), |
| | 131 | ), |
| | 132 | ]; |
| 138 | | 133 | |
| 139 | let output = git_pathspec::parse(input.as_bytes()); | 134 | check_valid_inputs(inputs) |
| 140 | assert!(output.is_err()); | 135 | } |
| 141 | assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); | | |
| 142 | } | 136 | } |
| 143 | | 137 | |
| 144 | #[test] | 138 | mod fail { |
| 145 | fn should_fail_on_invalid_keywords() { | 139 | use crate::is_valid_in_git; |
| 146 | let inputs = vec![ | 140 | use git_pathspec::parse::Error; |
| 147 | ":( )some/path", | 141 | |
| 148 | ":(tp)some/path", | 142 | #[test] |
| 149 | ":(top, exclude)some/path", | 143 | fn empty_input() { |
| 150 | ":(top,exclude,icse)some/path", | 144 | let input = ""; |
| 151 | ]; | | |
| 152 | | 145 | |
| 153 | inputs.into_iter().for_each(|input| { | | |
| 154 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); | 146 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); |
| 155 | | 147 | |
| 156 | let output = git_pathspec::parse(input.as_bytes()); | 148 | let output = git_pathspec::parse(input.as_bytes()); |
| 157 | assert!(output.is_err()); | 149 | assert!(output.is_err()); |
| 158 | assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); | 150 | assert!(matches!(output.unwrap_err(), Error::EmptyString { .. })); |
| 159 | }); | 151 | } |
| 160 | } | | |
| 161 | | 152 | |
| 162 | #[test] | 153 | #[test] |
| 163 | fn should_fail_on_invalid_attributes() { | 154 | fn invalid_keywords() { |
| 164 | let inputs = vec![ | 155 | let inputs = vec![ |
| 165 | ":(attr:+invalidAttr)some/path", | 156 | ":( )some/path", |
| 166 | ":(attr:validAttr +invalidAttr)some/path", | 157 | ":(tp)some/path", |
| 167 | ]; | 158 | ":(top, exclude)some/path", |
| | 159 | ":(top,exclude,icse)some/path", |
| | 160 | ]; |
| | 161 | |
| | 162 | inputs.into_iter().for_each(|input| { |
| | 163 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); |
| | 164 | |
| | 165 | let output = git_pathspec::parse(input.as_bytes()); |
| | 166 | assert!(output.is_err()); |
| | 167 | assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); |
| | 168 | }); |
| | 169 | } |
| 168 | | 170 | |
| 169 | for input in inputs { | 171 | #[test] |
| 170 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); | 172 | fn invalid_attributes() { |
| | 173 | let inputs = vec![ |
| | 174 | ":(attr:+invalidAttr)some/path", |
| | 175 | ":(attr:validAttr +invalidAttr)some/path", |
| | 176 | ]; |
| 171 | | 177 | |
| 172 | let output = git_pathspec::parse(input.as_bytes()); | 178 | for input in inputs { |
| 173 | assert!(output.is_err()); | 179 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); |
| 174 | assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); | 180 | |
| | 181 | let output = git_pathspec::parse(input.as_bytes()); |
| | 182 | assert!(output.is_err()); |
| | 183 | assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); |
| | 184 | } |
| 175 | } | 185 | } |
| 176 | } | | |
| 177 | | 186 | |
| 178 | #[test] | 187 | #[test] |
| 179 | fn should_fail_on_missing_parentheses() { | 188 | fn missing_parentheses() { |
| 180 | let input = ":(top"; | 189 | let input = ":(top"; |
| 181 | | 190 | |
| 182 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); | 191 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); |
| 183 | | 192 | |
| 184 | let output = git_pathspec::parse(input.as_bytes()); | 193 | let output = git_pathspec::parse(input.as_bytes()); |
| 185 | assert!(output.is_err()); | 194 | assert!(output.is_err()); |
| 186 | assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); | 195 | assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); |
| 187 | } | 196 | } |
| 188 | | 197 | |
| 189 | #[test] | 198 | #[test] |
| 190 | fn should_fail_on_glob_and_literal_present() { | 199 | fn glob_and_literal_keywords_present() { |
| 191 | let input = ":(glob,literal)some/path"; | 200 | let input = ":(glob,literal)some/path"; |
| 192 | | 201 | |
| 193 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); | 202 | assert!(!is_valid_in_git(input), "This pathspec is valid in git: {}", input); |
| 194 | | 203 | |
| 195 | let output = git_pathspec::parse(input.as_bytes()); | 204 | let output = git_pathspec::parse(input.as_bytes()); |
| 196 | assert!(output.is_err()); | 205 | assert!(output.is_err()); |
| 197 | assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchmodes)); | 206 | assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); |
| | 207 | } |
| 198 | } | 208 | } |
| 199 | | 209 | |
| 200 | // --- Helpers --- | | |
| 201 | | | |
| 202 | fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { | 210 | fn check_valid_inputs(inputs: Vec<(&str, Pattern)>) { |
| 203 | inputs.into_iter().for_each(|(input, expected)| { | 211 | inputs.into_iter().for_each(|(input, expected)| { |
| 204 | assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); | 212 | assert!(is_valid_in_git(input), "This pathspec is invalid in git: {}", input); |