1 | //! This tiny crate checks that the running or installed `rustc` meets some |
2 | //! version requirements. The version is queried by calling the Rust compiler |
3 | //! with `--version`. The path to the compiler is determined first via the |
4 | //! `RUSTC` environment variable. If it is not set, then `rustc` is used. If |
5 | //! that fails, no determination is made, and calls return `None`. |
6 | //! |
7 | //! # Examples |
8 | //! |
9 | //! * Set a `cfg` flag in `build.rs` if the running compiler was determined to |
10 | //! be at least version `1.13.0`: |
11 | //! |
12 | //! ```rust |
13 | //! extern crate version_check as rustc; |
14 | //! |
15 | //! if rustc::is_min_version("1.13.0" ).unwrap_or(false) { |
16 | //! println!("cargo:rustc-cfg=question_mark_operator" ); |
17 | //! } |
18 | //! ``` |
19 | //! |
20 | //! See [`is_max_version`] or [`is_exact_version`] to check if the compiler |
21 | //! is _at most_ or _exactly_ a certain version. |
22 | //! |
23 | //! * Check that the running compiler was released on or after `2018-12-18`: |
24 | //! |
25 | //! ```rust |
26 | //! extern crate version_check as rustc; |
27 | //! |
28 | //! match rustc::is_min_date("2018-12-18" ) { |
29 | //! Some(true) => "Yep! It's recent!" , |
30 | //! Some(false) => "No, it's older." , |
31 | //! None => "Couldn't determine the rustc version." |
32 | //! }; |
33 | //! ``` |
34 | //! |
35 | //! See [`is_max_date`] or [`is_exact_date`] to check if the compiler was |
36 | //! released _prior to_ or _exactly on_ a certain date. |
37 | //! |
38 | //! * Check that the running compiler supports feature flags: |
39 | //! |
40 | //! ```rust |
41 | //! extern crate version_check as rustc; |
42 | //! |
43 | //! match rustc::is_feature_flaggable() { |
44 | //! Some(true) => "Yes! It's a dev or nightly release!" , |
45 | //! Some(false) => "No, it's stable or beta." , |
46 | //! None => "Couldn't determine the rustc version." |
47 | //! }; |
48 | //! ``` |
49 | //! |
50 | //! * Check that the running compiler supports a specific feature: |
51 | //! |
52 | //! ```rust |
53 | //! extern crate version_check as rustc; |
54 | //! |
55 | //! if let Some(true) = rustc::supports_feature("doc_cfg" ) { |
56 | //! println!("cargo:rustc-cfg=has_doc_cfg" ); |
57 | //! } |
58 | //! ``` |
59 | //! |
60 | //! * Check that the running compiler is on the stable channel: |
61 | //! |
62 | //! ```rust |
63 | //! extern crate version_check as rustc; |
64 | //! |
65 | //! match rustc::Channel::read() { |
66 | //! Some(c) if c.is_stable() => format!("Yes! It's stable." ), |
67 | //! Some(c) => format!("No, the channel {} is not stable." , c), |
68 | //! None => format!("Couldn't determine the rustc version." ) |
69 | //! }; |
70 | //! ``` |
71 | //! |
72 | //! To interact with the version, release date, and release channel as structs, |
73 | //! use [`Version`], [`Date`], and [`Channel`], respectively. The [`triple()`] |
74 | //! function returns all three values efficiently. |
75 | //! |
76 | //! # Alternatives |
77 | //! |
78 | //! This crate is dead simple with no dependencies. If you need something more |
79 | //! and don't care about panicking if the version cannot be obtained, or if you |
80 | //! don't mind adding dependencies, see |
81 | //! [rustc_version](https://crates.io/crates/rustc_version). |
82 | |
83 | #![allow (deprecated)] |
84 | |
85 | mod version; |
86 | mod channel; |
87 | mod date; |
88 | |
89 | use std::env; |
90 | use std::process::Command; |
91 | |
92 | #[doc (inline)] pub use version::*; |
93 | #[doc (inline)] pub use channel::*; |
94 | #[doc (inline)] pub use date::*; |
95 | |
96 | /// Parses (version, date) as available from rustc version string. |
97 | fn version_and_date_from_rustc_version(s: &str) -> (Option<String>, Option<String>) { |
98 | let last_line: &str = s.lines().last().unwrap_or(default:s); |
99 | let mut components: Split<'_, &str> = last_line.trim().split(" " ); |
100 | let version: Option<&str> = components.nth(1); |
101 | let date: Option<&str> = componentsOption<&str>.filter(|c: &&str| c.ends_with(')' )).next() |
102 | .map(|s: &str| s.trim_right().trim_right_matches(")" ).trim_left().trim_left_matches('(' )); |
103 | (version.map(|s: &str| s.to_string()), date.map(|s: &str| s.to_string())) |
104 | } |
105 | |
106 | /// Parses (version, date) as available from rustc verbose version output. |
107 | fn version_and_date_from_rustc_verbose_version(s: &str) -> (Option<String>, Option<String>) { |
108 | let (mut version: Option, mut date: Option) = (None, None); |
109 | for line: &str in s.lines() { |
110 | let split: impl Fn(&str) -> Option = |s: &str| s.splitn(n:2, pat:":" ).nth(1).map(|s: &str| s.trim().to_string()); |
111 | match line.trim().split(" " ).nth(0) { |
112 | Some("rustc" ) => { |
113 | let (v: Option, d: Option) = version_and_date_from_rustc_version(line); |
114 | version = version.or(optb:v); |
115 | date = date.or(optb:d); |
116 | }, |
117 | Some("release:" ) => version = split(line), |
118 | Some("commit-date:" ) if line.ends_with("unknown" ) => date = None, |
119 | Some("commit-date:" ) => date = split(line), |
120 | _ => continue |
121 | } |
122 | } |
123 | |
124 | (version, date) |
125 | } |
126 | |
127 | /// Returns (version, date) as available from `rustc --version`. |
128 | fn get_version_and_date() -> Option<(Option<String>, Option<String>)> { |
129 | let rustc: String = env::var("RUSTC" ).unwrap_or_else(|_| "rustc" .to_string()); |
130 | CommandOption::new(program:rustc).arg("--verbose" ).arg("--version" ).output().ok() |
131 | .and_then(|output: Output| String::from_utf8(vec:output.stdout).ok()) |
132 | .map(|s: String| version_and_date_from_rustc_verbose_version(&s)) |
133 | } |
134 | |
135 | /// Reads the triple of [`Version`], [`Channel`], and [`Date`] of the installed |
136 | /// or running `rustc`. |
137 | /// |
138 | /// If any attribute cannot be determined (see the [top-level |
139 | /// documentation](crate)), returns `None`. |
140 | /// |
141 | /// To obtain only one of three attributes, use [`Version::read()`], |
142 | /// [`Channel::read()`], or [`Date::read()`]. |
143 | pub fn triple() -> Option<(Version, Channel, Date)> { |
144 | let (version_str: String, date_str: String) = match get_version_and_date() { |
145 | Some((Some(version: String), Some(date: String))) => (version, date), |
146 | _ => return None |
147 | }; |
148 | |
149 | // Can't use `?` or `try!` for `Option` in 1.0.0. |
150 | match Version::parse(&version_str) { |
151 | Some(version: Version) => match Channel::parse(&version_str) { |
152 | Some(channel: Channel) => match Date::parse(&date_str) { |
153 | Some(date: Date) => Some((version, channel, date)), |
154 | _ => None, |
155 | }, |
156 | _ => None, |
157 | }, |
158 | _ => None |
159 | } |
160 | } |
161 | |
162 | /// Checks that the running or installed `rustc` was released **on or after** |
163 | /// some date. |
164 | /// |
165 | /// The format of `min_date` must be YYYY-MM-DD. For instance: `2016-12-20` or |
166 | /// `2017-01-09`. |
167 | /// |
168 | /// If the date cannot be retrieved or parsed, or if `min_date` could not be |
169 | /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` |
170 | /// was release on or after `min_date` and `false` otherwise. |
171 | pub fn is_min_date(min_date: &str) -> Option<bool> { |
172 | match (Date::read(), Date::parse(min_date)) { |
173 | (Some(rustc_date: Date), Some(min_date: Date)) => Some(rustc_date >= min_date), |
174 | _ => None |
175 | } |
176 | } |
177 | |
178 | /// Checks that the running or installed `rustc` was released **on or before** |
179 | /// some date. |
180 | /// |
181 | /// The format of `max_date` must be YYYY-MM-DD. For instance: `2016-12-20` or |
182 | /// `2017-01-09`. |
183 | /// |
184 | /// If the date cannot be retrieved or parsed, or if `max_date` could not be |
185 | /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` |
186 | /// was release on or before `max_date` and `false` otherwise. |
187 | pub fn is_max_date(max_date: &str) -> Option<bool> { |
188 | match (Date::read(), Date::parse(max_date)) { |
189 | (Some(rustc_date: Date), Some(max_date: Date)) => Some(rustc_date <= max_date), |
190 | _ => None |
191 | } |
192 | } |
193 | |
194 | /// Checks that the running or installed `rustc` was released **exactly** on |
195 | /// some date. |
196 | /// |
197 | /// The format of `date` must be YYYY-MM-DD. For instance: `2016-12-20` or |
198 | /// `2017-01-09`. |
199 | /// |
200 | /// If the date cannot be retrieved or parsed, or if `date` could not be parsed, |
201 | /// returns `None`. Otherwise returns `true` if the installed `rustc` was |
202 | /// release on `date` and `false` otherwise. |
203 | pub fn is_exact_date(date: &str) -> Option<bool> { |
204 | match (Date::read(), Date::parse(date)) { |
205 | (Some(rustc_date: Date), Some(date: Date)) => Some(rustc_date == date), |
206 | _ => None |
207 | } |
208 | } |
209 | |
210 | /// Checks that the running or installed `rustc` is **at least** some minimum |
211 | /// version. |
212 | /// |
213 | /// The format of `min_version` is a semantic version: `1.3.0`, `1.15.0-beta`, |
214 | /// `1.14.0`, `1.16.0-nightly`, etc. |
215 | /// |
216 | /// If the version cannot be retrieved or parsed, or if `min_version` could not |
217 | /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc` |
218 | /// is at least `min_version` and `false` otherwise. |
219 | pub fn is_min_version(min_version: &str) -> Option<bool> { |
220 | match (Version::read(), Version::parse(min_version)) { |
221 | (Some(rustc_ver: Version), Some(min_ver: Version)) => Some(rustc_ver >= min_ver), |
222 | _ => None |
223 | } |
224 | } |
225 | |
226 | /// Checks that the running or installed `rustc` is **at most** some maximum |
227 | /// version. |
228 | /// |
229 | /// The format of `max_version` is a semantic version: `1.3.0`, `1.15.0-beta`, |
230 | /// `1.14.0`, `1.16.0-nightly`, etc. |
231 | /// |
232 | /// If the version cannot be retrieved or parsed, or if `max_version` could not |
233 | /// be parsed, returns `None`. Otherwise returns `true` if the installed `rustc` |
234 | /// is at most `max_version` and `false` otherwise. |
235 | pub fn is_max_version(max_version: &str) -> Option<bool> { |
236 | match (Version::read(), Version::parse(max_version)) { |
237 | (Some(rustc_ver: Version), Some(max_ver: Version)) => Some(rustc_ver <= max_ver), |
238 | _ => None |
239 | } |
240 | } |
241 | |
242 | /// Checks that the running or installed `rustc` is **exactly** some version. |
243 | /// |
244 | /// The format of `version` is a semantic version: `1.3.0`, `1.15.0-beta`, |
245 | /// `1.14.0`, `1.16.0-nightly`, etc. |
246 | /// |
247 | /// If the version cannot be retrieved or parsed, or if `version` could not be |
248 | /// parsed, returns `None`. Otherwise returns `true` if the installed `rustc` is |
249 | /// exactly `version` and `false` otherwise. |
250 | pub fn is_exact_version(version: &str) -> Option<bool> { |
251 | match (Version::read(), Version::parse(version)) { |
252 | (Some(rustc_ver: Version), Some(version: Version)) => Some(rustc_ver == version), |
253 | _ => None |
254 | } |
255 | } |
256 | |
257 | /// Checks whether the running or installed `rustc` supports feature flags. |
258 | /// |
259 | /// In other words, if the channel is either "nightly" or "dev". |
260 | /// |
261 | /// Note that support for specific `rustc` features can be enabled or disabled |
262 | /// via the `allow-features` compiler flag, which this function _does not_ |
263 | /// check. That is, this function _does not_ check whether a _specific_ feature |
264 | /// is supported, but instead whether features are supported at all. To check |
265 | /// for support for a specific feature, use [`supports_feature()`]. |
266 | /// |
267 | /// If the version could not be determined, returns `None`. Otherwise returns |
268 | /// `true` if the running version supports feature flags and `false` otherwise. |
269 | pub fn is_feature_flaggable() -> Option<bool> { |
270 | Channel::read().map(|c: Channel| c.supports_features()) |
271 | } |
272 | |
273 | /// Checks whether the running or installed `rustc` supports `feature`. |
274 | /// |
275 | /// Returns _true_ _iff_ [`is_feature_flaggable()`] returns `true` _and_ the |
276 | /// feature is not disabled via exclusion in `allow-features` via `RUSTFLAGS` or |
277 | /// `CARGO_ENCODED_RUSTFLAGS`. If the version could not be determined, returns |
278 | /// `None`. |
279 | /// |
280 | /// # Example |
281 | /// |
282 | /// ```rust |
283 | /// use version_check as rustc; |
284 | /// |
285 | /// if let Some(true) = rustc::supports_feature("doc_cfg" ) { |
286 | /// println!("cargo:rustc-cfg=has_doc_cfg" ); |
287 | /// } |
288 | /// ``` |
289 | pub fn supports_feature(feature: &str) -> Option<bool> { |
290 | match is_feature_flaggable() { |
291 | Some(true) => { /* continue */ } |
292 | Some(false) => return Some(false), |
293 | None => return None, |
294 | } |
295 | |
296 | let env_flags = env::var_os("CARGO_ENCODED_RUSTFLAGS" ) |
297 | .map(|flags| (flags, ' \x1f' )) |
298 | .or_else(|| env::var_os("RUSTFLAGS" ).map(|flags| (flags, ' ' ))); |
299 | |
300 | if let Some((flags, delim)) = env_flags { |
301 | const ALLOW_FEATURES: &'static str = "allow-features=" ; |
302 | |
303 | let rustflags = flags.to_string_lossy(); |
304 | let allow_features = rustflags.split(delim) |
305 | .map(|flag| flag.trim_left_matches("-Z" ).trim()) |
306 | .filter(|flag| flag.starts_with(ALLOW_FEATURES)) |
307 | .map(|flag| &flag[ALLOW_FEATURES.len()..]); |
308 | |
309 | if let Some(allow_features) = allow_features.last() { |
310 | return Some(allow_features.split(',' ).any(|f| f.trim() == feature)); |
311 | } |
312 | } |
313 | |
314 | // If there are no `RUSTFLAGS` or `CARGO_ENCODED_RUSTFLAGS` or they don't |
315 | // contain an `allow-features` flag, assume compiler allows all features. |
316 | Some(true) |
317 | } |
318 | |
319 | #[cfg (test)] |
320 | mod tests { |
321 | use std::{env, fs}; |
322 | |
323 | use super::version_and_date_from_rustc_version; |
324 | use super::version_and_date_from_rustc_verbose_version; |
325 | |
326 | macro_rules! check_parse { |
327 | (@ $f:expr, $s:expr => $v:expr, $d:expr) => ({ |
328 | if let (Some(v), d) = $f(&$s) { |
329 | let e_d: Option<&str> = $d.into(); |
330 | assert_eq!((v, d), ($v.to_string(), e_d.map(|s| s.into()))); |
331 | } else { |
332 | panic!("{:?} didn't parse for version testing." , $s); |
333 | } |
334 | }); |
335 | ($f:expr, $s:expr => $v:expr, $d:expr) => ({ |
336 | let warn = "warning: invalid logging spec 'warning', ignoring it" ; |
337 | let warn2 = "warning: sorry, something went wrong :(sad)" ; |
338 | check_parse!(@ $f, $s => $v, $d); |
339 | check_parse!(@ $f, &format!("{} \n{}" , warn, $s) => $v, $d); |
340 | check_parse!(@ $f, &format!("{} \n{}" , warn2, $s) => $v, $d); |
341 | check_parse!(@ $f, &format!("{} \n{} \n{}" , warn, warn2, $s) => $v, $d); |
342 | check_parse!(@ $f, &format!("{} \n{} \n{}" , warn2, warn, $s) => $v, $d); |
343 | }) |
344 | } |
345 | |
346 | macro_rules! check_terse_parse { |
347 | ($($s:expr => $v:expr, $d:expr,)+) => {$( |
348 | check_parse!(version_and_date_from_rustc_version, $s => $v, $d); |
349 | )+} |
350 | } |
351 | |
352 | macro_rules! check_verbose_parse { |
353 | ($($s:expr => $v:expr, $d:expr,)+) => {$( |
354 | check_parse!(version_and_date_from_rustc_verbose_version, $s => $v, $d); |
355 | )+} |
356 | } |
357 | |
358 | #[test ] |
359 | fn test_version_parse() { |
360 | check_terse_parse! { |
361 | "rustc 1.18.0" => "1.18.0" , None, |
362 | "rustc 1.8.0" => "1.8.0" , None, |
363 | "rustc 1.20.0-nightly" => "1.20.0-nightly" , None, |
364 | "rustc 1.20" => "1.20" , None, |
365 | "rustc 1.3" => "1.3" , None, |
366 | "rustc 1" => "1" , None, |
367 | "rustc 1.5.1-beta" => "1.5.1-beta" , None, |
368 | "rustc 1.20.0 (2017-07-09)" => "1.20.0" , Some("2017-07-09" ), |
369 | "rustc 1.20.0-dev (2017-07-09)" => "1.20.0-dev" , Some("2017-07-09" ), |
370 | "rustc 1.20.0-nightly (d84693b93 2017-07-09)" => "1.20.0-nightly" , Some("2017-07-09" ), |
371 | "rustc 1.20.0 (d84693b93 2017-07-09)" => "1.20.0" , Some("2017-07-09" ), |
372 | "rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)" => "1.30.0-nightly" , Some("2018-09-20" ), |
373 | }; |
374 | } |
375 | |
376 | #[test ] |
377 | fn test_verbose_version_parse() { |
378 | check_verbose_parse! { |
379 | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) \n\ |
380 | binary: rustc \n\ |
381 | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e \n\ |
382 | commit-date: 2015-05-13 \n\ |
383 | build-date: 2015-05-14 \n\ |
384 | host: x86_64-unknown-linux-gnu \n\ |
385 | release: 1.0.0" => "1.0.0" , Some("2015-05-13" ), |
386 | |
387 | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) \n\ |
388 | commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e \n\ |
389 | commit-date: 2015-05-13 \n\ |
390 | build-date: 2015-05-14 \n\ |
391 | host: x86_64-unknown-linux-gnu \n\ |
392 | release: 1.0.0" => "1.0.0" , Some("2015-05-13" ), |
393 | |
394 | "rustc 1.50.0 (cb75ad5db 2021-02-10) \n\ |
395 | binary: rustc \n\ |
396 | commit-hash: cb75ad5db02783e8b0222fee363c5f63f7e2cf5b \n\ |
397 | commit-date: 2021-02-10 \n\ |
398 | host: x86_64-unknown-linux-gnu \n\ |
399 | release: 1.50.0" => "1.50.0" , Some("2021-02-10" ), |
400 | |
401 | "rustc 1.52.0-nightly (234781afe 2021-03-07) \n\ |
402 | binary: rustc \n\ |
403 | commit-hash: 234781afe33d3f339b002f85f948046d8476cfc9 \n\ |
404 | commit-date: 2021-03-07 \n\ |
405 | host: x86_64-unknown-linux-gnu \n\ |
406 | release: 1.52.0-nightly \n\ |
407 | LLVM version: 12.0.0" => "1.52.0-nightly" , Some("2021-03-07" ), |
408 | |
409 | "rustc 1.41.1 \n\ |
410 | binary: rustc \n\ |
411 | commit-hash: unknown \n\ |
412 | commit-date: unknown \n\ |
413 | host: x86_64-unknown-linux-gnu \n\ |
414 | release: 1.41.1 \n\ |
415 | LLVM version: 7.0" => "1.41.1" , None, |
416 | |
417 | "rustc 1.49.0 \n\ |
418 | binary: rustc \n\ |
419 | commit-hash: unknown \n\ |
420 | commit-date: unknown \n\ |
421 | host: x86_64-unknown-linux-gnu \n\ |
422 | release: 1.49.0" => "1.49.0" , None, |
423 | |
424 | "rustc 1.50.0 (Fedora 1.50.0-1.fc33) \n\ |
425 | binary: rustc \n\ |
426 | commit-hash: unknown \n\ |
427 | commit-date: unknown \n\ |
428 | host: x86_64-unknown-linux-gnu \n\ |
429 | release: 1.50.0" => "1.50.0" , None, |
430 | }; |
431 | } |
432 | |
433 | fn read_static(verbose: bool, channel: &str, minor: usize) -> String { |
434 | use std::fs::File; |
435 | use std::path::Path; |
436 | use std::io::{BufReader, Read}; |
437 | |
438 | let subdir = if verbose { "verbose" } else { "terse" }; |
439 | let path = Path::new(STATIC_PATH) |
440 | .join(channel) |
441 | .join(subdir) |
442 | .join(format!("rustc-1. {}.0" , minor)); |
443 | |
444 | let file = File::open(path).unwrap(); |
445 | let mut buf_reader = BufReader::new(file); |
446 | let mut contents = String::new(); |
447 | buf_reader.read_to_string(&mut contents).unwrap(); |
448 | contents |
449 | } |
450 | |
451 | static STATIC_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR" ), "/static" ); |
452 | |
453 | static DATES: [&'static str; 51] = [ |
454 | "2015-05-13" , "2015-06-19" , "2015-08-03" , "2015-09-15" , "2015-10-27" , |
455 | "2015-12-04" , "2016-01-19" , "2016-02-29" , "2016-04-11" , "2016-05-18" , |
456 | "2016-07-03" , "2016-08-15" , "2016-09-23" , "2016-11-07" , "2016-12-16" , |
457 | "2017-01-19" , "2017-03-10" , "2017-04-24" , "2017-06-06" , "2017-07-17" , |
458 | "2017-08-27" , "2017-10-09" , "2017-11-20" , "2018-01-01" , "2018-02-12" , |
459 | "2018-03-25" , "2018-05-07" , "2018-06-19" , "2018-07-30" , "2018-09-11" , |
460 | "2018-10-24" , "2018-12-04" , "2019-01-16" , "2019-02-28" , "2019-04-10" , |
461 | "2019-05-20" , "2019-07-03" , "2019-08-13" , "2019-09-23" , "2019-11-04" , |
462 | "2019-12-16" , "2020-01-27" , "2020-03-09" , "2020-04-20" , "2020-06-01" , |
463 | "2020-07-13" , "2020-08-24" , "2020-10-07" , "2020-11-16" , "2020-12-29" , |
464 | "2021-02-10" , |
465 | ]; |
466 | |
467 | #[test ] |
468 | fn test_stable_compatibility() { |
469 | if env::var_os("FORCE_STATIC" ).is_none() && fs::metadata(STATIC_PATH).is_err() { |
470 | // We exclude `/static` when we package `version_check`, so don't |
471 | // run if static files aren't present unless we know they should be. |
472 | return; |
473 | } |
474 | |
475 | // Ensure we can parse all output from all Linux stable releases. |
476 | for v in 0..DATES.len() { |
477 | let (version, date) = (&format!("1. {}.0" , v), Some(DATES[v])); |
478 | check_terse_parse!(read_static(false, "stable" , v) => version, date,); |
479 | check_verbose_parse!(read_static(true, "stable" , v) => version, date,); |
480 | } |
481 | } |
482 | |
483 | #[test ] |
484 | fn test_parse_current() { |
485 | let (version, channel) = (::Version::read(), ::Channel::read()); |
486 | assert!(version.is_some()); |
487 | assert!(channel.is_some()); |
488 | |
489 | if let Ok(known_channel) = env::var("KNOWN_CHANNEL" ) { |
490 | assert_eq!(channel, ::Channel::parse(&known_channel)); |
491 | } |
492 | } |
493 | } |
494 | |