1use pest::iterators::{Pair, Pairs};
18use pest_derive::Parser;
19use std::collections::HashSet;
20use std::convert::TryFrom;
21use std::fs;
22use std::path::Path;
23use thiserror::Error;
24
25type PestError = pest::error::Error<Rule>;
26
27#[derive(Error, Debug)]
29pub enum DTraceError {
30 #[error("Unexpected token type, expected {expected:?}, found {found:?}")]
31 UnexpectedToken { expected: Rule, found: Rule },
32 #[error("This set of pairs contains no tokens")]
33 EmptyPairsIterator,
34 #[error("Provider and probe name pairs must be unique: duplicated \"{0:?}\"")]
35 DuplicateProbeName((String, String)),
36 #[error("The provider name \"{0}\" is invalid")]
37 InvalidProviderName(String),
38 #[error("The probe name \"{0}\" is invalid")]
39 InvalidProbeName(String),
40 #[error(transparent)]
41 IO(#[from] std::io::Error),
42 #[error("Input is not a valid DTrace provider definition:\n{0}")]
43 ParseError(#[from] Box<PestError>),
44}
45
46#[derive(Parser, Debug)]
47#[grammar = "dtrace.pest"]
48struct DTraceParser;
49
50fn expect_token(pair: &Pair<'_, Rule>, rule: Rule) -> Result<(), DTraceError> {
52 if pair.as_rule() == rule {
53 Ok(())
54 } else {
55 Err(DTraceError::UnexpectedToken {
56 expected: rule,
57 found: pair.as_rule(),
58 })
59 }
60}
61
62#[derive(Clone, Copy, Debug, PartialEq)]
64pub enum BitWidth {
65 Bit8,
66 Bit16,
67 Bit32,
68 Bit64,
69 Pointer,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq)]
75pub enum Sign {
76 Signed,
77 Unsigned,
78}
79
80#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct Integer {
83 pub sign: Sign,
84 pub width: BitWidth,
85}
86
87const RUST_TYPE_PREFIX: &str = "::std::os::raw::c_";
88
89impl Integer {
90 fn width_to_c_str(&self) -> &'static str {
91 match self.width {
92 BitWidth::Bit8 => "8",
93 BitWidth::Bit16 => "16",
94 BitWidth::Bit32 => "32",
95 BitWidth::Bit64 => "64",
96 #[cfg(target_pointer_width = "32")]
97 BitWidth::Pointer => "32",
98 #[cfg(target_pointer_width = "64")]
99 BitWidth::Pointer => "64",
100 #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
101 BitWidth::Pointer => compile_error!("Unsupported pointer width"),
102 }
103 }
104
105 pub fn to_c_type(&self) -> String {
106 let prefix = match self.sign {
107 Sign::Unsigned => "u",
108 _ => "",
109 };
110 format!("{prefix}int{}_t", self.width_to_c_str())
111 }
112
113 pub fn to_rust_ffi_type(&self) -> String {
114 let ty = match (self.sign, self.width) {
115 (Sign::Unsigned, BitWidth::Bit8) => "uchar",
116 (Sign::Unsigned, BitWidth::Bit16) => "ushort",
117 (Sign::Unsigned, BitWidth::Bit32) => "uint",
118 (Sign::Unsigned, BitWidth::Bit64) => "ulonglong",
119 #[cfg(target_pointer_width = "32")]
120 (Sign::Unsigned, BitWidth::Pointer) => "uint",
121 #[cfg(target_pointer_width = "64")]
122 (Sign::Unsigned, BitWidth::Pointer) => "ulonglong",
123 (Sign::Signed, BitWidth::Bit8) => "schar",
124 (Sign::Signed, BitWidth::Bit16) => "short",
125 (Sign::Signed, BitWidth::Bit32) => "int",
126 (Sign::Signed, BitWidth::Bit64) => "longlong",
127 #[cfg(target_pointer_width = "32")]
128 (Sign::Signed, BitWidth::Pointer) => "int",
129 #[cfg(target_pointer_width = "64")]
130 (Sign::Signed, BitWidth::Pointer) => "longlong",
131
132 #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
133 (_, BitWidth::Pointer) => compile_error!("Unsupported pointer width"),
134 };
135 format!("{RUST_TYPE_PREFIX}{ty}")
136 }
137
138 fn width_to_str(&self) -> &'static str {
139 match self.width {
140 BitWidth::Pointer => "size",
141 _ => self.width_to_c_str(),
142 }
143 }
144
145 pub fn to_rust_type(&self) -> String {
146 let prefix = match self.sign {
147 Sign::Signed => "i",
148 Sign::Unsigned => "u",
149 };
150 format!("{prefix}{}", self.width_to_str())
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq)]
156pub enum DataType {
157 Integer(Integer),
158 Pointer(Integer),
159 String,
160}
161
162impl From<Pair<'_, Rule>> for Integer {
163 fn from(integer_type: Pair<'_, Rule>) -> Integer {
164 let sign = match integer_type.as_rule() {
165 Rule::SIGNED_INT => Sign::Signed,
166 Rule::UNSIGNED_INT => Sign::Unsigned,
167 _ => unreachable!("Expected a signed or unsigned integer"),
168 };
169 let width = match integer_type.into_inner().as_str() {
170 "8" => BitWidth::Bit8,
171 "16" => BitWidth::Bit16,
172 "32" => BitWidth::Bit32,
173 "64" => BitWidth::Bit64,
174 "ptr" => BitWidth::Pointer,
175 _ => unreachable!("Expected a bit width"),
176 };
177 Integer { sign, width }
178 }
179}
180
181impl TryFrom<&Pair<'_, Rule>> for DataType {
182 type Error = DTraceError;
183
184 fn try_from(pair: &Pair<'_, Rule>) -> Result<DataType, Self::Error> {
185 expect_token(pair, Rule::DATA_TYPE)?;
186 let inner = pair
187 .clone()
188 .into_inner()
189 .next()
190 .expect("Data type token is expected to contain a concrete type");
191 let typ = match inner.as_rule() {
192 Rule::INTEGER => {
193 let integer = pair
194 .clone()
195 .into_inner()
196 .next()
197 .expect("Expected a signed or unsigned integral type");
198 assert!(matches!(integer.as_rule(), Rule::INTEGER));
199 DataType::Integer(Integer::from(
200 integer
201 .clone()
202 .into_inner()
203 .next()
204 .expect("Expected an integral type"),
205 ))
206 }
207 Rule::INTEGER_POINTER => {
208 let pointer = pair
209 .clone()
210 .into_inner()
211 .next()
212 .expect("Expected a pointer to a signed or unsigned integral type");
213 assert!(matches!(pointer.as_rule(), Rule::INTEGER_POINTER));
214 let mut parts = pointer.clone().into_inner();
215 let integer = parts
216 .next()
217 .expect("Expected a signed or unsigned integral type");
218 let star = parts.next().expect("Expected a literal `*`");
219 assert_eq!(star.as_rule(), Rule::STAR);
220 DataType::Pointer(Integer::from(
221 integer
222 .clone()
223 .into_inner()
224 .next()
225 .expect("Expected an integral type"),
226 ))
227 }
228 Rule::STRING => DataType::String,
229 _ => unreachable!("Parsed an unexpected DATA_TYPE token"),
230 };
231 Ok(typ)
232 }
233}
234
235impl TryFrom<&Pairs<'_, Rule>> for DataType {
236 type Error = DTraceError;
237
238 fn try_from(pairs: &Pairs<'_, Rule>) -> Result<DataType, Self::Error> {
239 DataType::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
240 }
241}
242
243impl DataType {
244 pub fn to_c_type(&self) -> String {
246 match self {
247 DataType::Integer(int) => int.to_c_type(),
248 DataType::Pointer(int) => format!("{}*", int.to_c_type()),
249 DataType::String => String::from("char*"),
250 }
251 }
252
253 pub fn to_rust_ffi_type(&self) -> String {
255 match self {
256 DataType::Integer(int) => int.to_rust_ffi_type(),
257 DataType::Pointer(int) => format!("*const {}", int.to_rust_ffi_type()),
258 DataType::String => format!("*const {RUST_TYPE_PREFIX}char"),
259 }
260 }
261
262 pub fn to_rust_type(&self) -> String {
264 match self {
265 DataType::Integer(int) => int.to_rust_type(),
266 DataType::Pointer(int) => format!("*const {}", int.to_rust_type()),
267 DataType::String => String::from("&str"),
268 }
269 }
270}
271
272#[derive(Clone, Debug, PartialEq)]
274pub struct Probe {
275 pub name: String,
276 pub types: Vec<DataType>,
277}
278
279impl TryFrom<&Pair<'_, Rule>> for Probe {
280 type Error = DTraceError;
281
282 fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
283 expect_token(pair, Rule::PROBE)?;
284 let mut inner = pair.clone().into_inner();
285 expect_token(
286 &inner.next().expect("Expected the literal 'probe'"),
287 Rule::PROBE_KEY,
288 )?;
289 let token = inner.next().expect("Expected a probe name");
290 let name = token.as_str().to_string();
291 if name == "probe" || name == "start" {
292 return Err(DTraceError::InvalidProbeName(name));
293 }
294 expect_token(
295 &inner.next().expect("Expected the literal '('"),
296 Rule::LEFT_PAREN,
297 )?;
298 let possibly_argument_list = inner
299 .next()
300 .expect("Expected an argument list or literal ')'");
301 let mut types = Vec::new();
302 if expect_token(&possibly_argument_list, Rule::ARGUMENT_LIST).is_ok() {
303 let arguments = possibly_argument_list.clone().into_inner();
304 for data_type in arguments {
305 expect_token(&data_type, Rule::DATA_TYPE)?;
306 types.push(DataType::try_from(&data_type)?);
307 }
308 }
309 expect_token(
310 &inner.next().expect("Expected a literal ')'"),
311 Rule::RIGHT_PAREN,
312 )?;
313 expect_token(
314 &inner.next().expect("Expected a literal ';'"),
315 Rule::SEMICOLON,
316 )?;
317 Ok(Probe { name, types })
318 }
319}
320
321impl TryFrom<&Pairs<'_, Rule>> for Probe {
322 type Error = DTraceError;
323
324 fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
325 Probe::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
326 }
327}
328
329#[derive(Debug, Clone, PartialEq)]
331pub struct Provider {
332 pub name: String,
333 pub probes: Vec<Probe>,
334}
335
336impl TryFrom<&Pair<'_, Rule>> for Provider {
337 type Error = DTraceError;
338
339 fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
340 expect_token(pair, Rule::PROVIDER)?;
341 let mut inner = pair.clone().into_inner();
342 expect_token(
343 &inner.next().expect("Expected the literal 'provider'"),
344 Rule::PROVIDER_KEY,
345 )?;
346 let name = inner
347 .next()
348 .expect("Expected a provider name")
349 .as_str()
350 .to_string();
351 if name == "provider" {
352 return Err(DTraceError::InvalidProviderName(name));
353 }
354 expect_token(
355 &inner.next().expect("Expected the literal '{'"),
356 Rule::LEFT_BRACE,
357 )?;
358 let mut probes = Vec::new();
359 let mut possibly_probe = inner
360 .next()
361 .expect("Expected at least one probe in the provider");
362 while expect_token(&possibly_probe, Rule::PROBE).is_ok() {
363 probes.push(Probe::try_from(&possibly_probe)?);
364 possibly_probe = inner.next().expect("Expected a token");
365 }
366 expect_token(&possibly_probe, Rule::RIGHT_BRACE)?;
367 expect_token(
368 &inner.next().expect("Expected a literal ';'"),
369 Rule::SEMICOLON,
370 )?;
371 Ok(Provider { name, probes })
372 }
373}
374
375impl TryFrom<&Pairs<'_, Rule>> for Provider {
376 type Error = DTraceError;
377
378 fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
379 Provider::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
380 }
381}
382
383#[derive(Debug, Clone, PartialEq)]
385pub struct File {
386 name: String,
387 providers: Vec<Provider>,
388}
389
390impl TryFrom<&Pair<'_, Rule>> for File {
391 type Error = DTraceError;
392
393 fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
394 expect_token(pair, Rule::FILE)?;
395 let mut providers = Vec::new();
396 let mut names = HashSet::new();
397 for item in pair.clone().into_inner() {
398 if item.as_rule() == Rule::PROVIDER {
399 let provider = Provider::try_from(&item)?;
400 for probe in provider.probes.iter() {
401 let name = (provider.name.clone(), probe.name.clone());
402 if names.contains(&name) {
403 return Err(DTraceError::DuplicateProbeName(name));
404 }
405 names.insert(name.clone());
406 }
407 providers.push(provider);
408 }
409 }
410
411 Ok(File {
412 name: "".to_string(),
413 providers,
414 })
415 }
416}
417
418impl TryFrom<&Pairs<'_, Rule>> for File {
419 type Error = DTraceError;
420
421 fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
422 File::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
423 }
424}
425
426impl File {
427 pub fn from_file(filename: &Path) -> Result<Self, DTraceError> {
429 let mut f = File::try_from(fs::read_to_string(filename)?.as_str())?;
430 f.name = filename
431 .file_stem()
432 .unwrap()
433 .to_os_string()
434 .into_string()
435 .unwrap();
436 Ok(f)
437 }
438
439 pub fn name(&self) -> &String {
441 &self.name
442 }
443
444 pub fn providers(&self) -> &Vec<Provider> {
446 &self.providers
447 }
448}
449
450impl TryFrom<&str> for File {
451 type Error = DTraceError;
452
453 fn try_from(s: &str) -> Result<Self, Self::Error> {
454 use pest::Parser;
455 File::try_from(&DTraceParser::parse(Rule::FILE, s).map_err(|e| {
456 Box::new(e.renamed_rules(|rule| match *rule {
457 Rule::DATA_TYPE | Rule::BIT_WIDTH => {
458 format!(
459 "{:?}.\n\n{}",
460 *rule,
461 concat!(
462 "Unsupported type, the following are supported:\n",
463 " - uint8_t\n",
464 " - uint16_t\n",
465 " - uint32_t\n",
466 " - uint64_t\n",
467 " - int8_t\n",
468 " - int16_t\n",
469 " - int32_t\n",
470 " - int64_t\n",
471 " - &str\n",
472 )
473 )
474 }
475 _ => format!("{:?}", rule),
476 }))
477 })?)
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::BitWidth;
484 use super::DTraceParser;
485 use super::DataType;
486 use super::File;
487 use super::Integer;
488 use super::Probe;
489 use super::Provider;
490 use super::Rule;
491 use super::Sign;
492 use super::TryFrom;
493 use ::pest::Parser;
494 use rstest::{fixture, rstest};
495
496 #[rstest(
497 token,
498 rule,
499 case("probe", Rule::PROBE_KEY),
500 case("provider", Rule::PROVIDER_KEY),
501 case(";", Rule::SEMICOLON),
502 case("(", Rule::LEFT_PAREN),
503 case(")", Rule::RIGHT_PAREN),
504 case("{", Rule::LEFT_BRACE),
505 case("}", Rule::RIGHT_BRACE)
506 )]
507 fn test_basic_tokens(token: &str, rule: Rule) {
508 assert!(DTraceParser::parse(rule, token).is_ok());
509 }
510
511 #[test]
512 #[should_panic]
513 fn test_bad_basic_token() {
514 assert!(DTraceParser::parse(Rule::LEFT_BRACE, "x").is_ok())
515 }
516
517 #[test]
518 fn test_identifier() {
519 assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo").is_ok());
520 assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo_bar").is_ok());
521 assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo9").is_ok());
522
523 assert!(DTraceParser::parse(Rule::IDENTIFIER, "_bar").is_err());
524 assert!(DTraceParser::parse(Rule::IDENTIFIER, "").is_err());
525 assert!(DTraceParser::parse(Rule::IDENTIFIER, "9foo").is_err());
526 }
527
528 #[test]
529 fn test_data_types() {
530 assert!(DTraceParser::parse(Rule::DATA_TYPE, "uint8_t").is_ok());
531 assert!(DTraceParser::parse(Rule::DATA_TYPE, "int").is_err());
532 assert!(DTraceParser::parse(Rule::DATA_TYPE, "flaot").is_err());
533 }
534
535 #[test]
536 fn test_probe() {
537 let defn = "probe foo(uint8_t, uint16_t, uint16_t);";
538 assert!(DTraceParser::parse(Rule::PROBE, defn).is_ok());
539 assert!(DTraceParser::parse(Rule::PROBE, &defn[..defn.len() - 2]).is_err());
540 }
541
542 #[test]
543 fn test_basic_provider() {
544 let defn = r#"
545 provider foo {
546 probe bar();
547 probe baz(char*, uint16_t, uint8_t);
548 };"#;
549 println!("{:?}", DTraceParser::parse(Rule::FILE, defn));
550 assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
551 assert!(DTraceParser::parse(Rule::FILE, &defn[..defn.len() - 2]).is_err());
552 }
553
554 #[test]
555 fn test_null_provider() {
556 let defn = "provider foo { };";
557 assert!(DTraceParser::parse(Rule::FILE, defn).is_err());
558 }
559
560 #[test]
561 fn test_comment_provider() {
562 let defn = r#"
563 /* Check out this fly provider */
564 provider foo {
565 probe bar();
566 probe baz(char*, uint16_t, uint8_t);
567 };"#;
568 assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
569 }
570
571 #[test]
572 fn test_pragma_provider() {
573 let defn = r#"
574 #pragma I am a robot
575 provider foo {
576 probe bar();
577 probe baz(char*, uint16_t, uint8_t);
578 };
579 "#;
580 println!("{}", defn);
581 assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
582 }
583
584 #[test]
585 fn test_two_providers() {
586 let defn = r#"
587 provider foo {
588 probe bar();
589 probe baz(char*, uint16_t, uint8_t);
590 };
591 provider bar {
592 probe bar();
593 probe baz(char*, uint16_t, uint8_t);
594 };
595 "#;
596 println!("{}", defn);
597 assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
598 }
599
600 #[rstest(
601 defn,
602 data_type,
603 case("uint8_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8 })),
604 case("uint16_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16 })),
605 case("uint32_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32 })),
606 case("uint64_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64 })),
607 case("uintptr_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Pointer })),
608 case("int8_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit8 })),
609 case("int16_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit16 })),
610 case("int32_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit32 })),
611 case("int64_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit64 })),
612 case("intptr_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Pointer })),
613 case("uint8_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8})),
614 case("uint16_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16})),
615 case("uint32_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32})),
616 case("uint64_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64})),
617 case("int8_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit8})),
618 case("int16_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit16})),
619 case("int32_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit32})),
620 case("int64_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit64})),
621 case("char*", DataType::String)
622 )]
623 fn test_data_type_enum(defn: &str, data_type: DataType) {
624 let dtype =
625 DataType::try_from(&DTraceParser::parse(Rule::DATA_TYPE, defn).unwrap()).unwrap();
626 assert_eq!(dtype, data_type);
627 }
628
629 #[test]
630 fn test_data_type_conversion() {
631 let dtype =
632 DataType::try_from(&DTraceParser::parse(Rule::DATA_TYPE, "uint8_t").unwrap()).unwrap();
633 assert_eq!(dtype.to_rust_ffi_type(), "::std::os::raw::c_uchar");
634 }
635
636 #[fixture]
637 fn probe_data() -> (String, String) {
638 let provider = String::from("foo");
639 let probe = String::from("probe baz(char*, uint16_t, uint8_t*);");
640 (provider, probe)
641 }
642
643 #[fixture]
644 fn probe(probe_data: (String, String)) -> (String, Probe) {
645 (
646 probe_data.0,
647 Probe::try_from(&DTraceParser::parse(Rule::PROBE, &probe_data.1).unwrap()).unwrap(),
648 )
649 }
650
651 #[rstest]
652 fn test_probe_struct_parse(probe_data: (String, String)) {
653 let (_, probe) = probe_data;
654 let probe = Probe::try_from(&DTraceParser::parse(Rule::PROBE, &probe).unwrap())
655 .expect("Could not parse probe tokens");
656 assert_eq!(probe.name, "baz");
657 assert_eq!(
658 probe.types,
659 &[
660 DataType::String,
661 DataType::Integer(Integer {
662 sign: Sign::Unsigned,
663 width: BitWidth::Bit16,
664 }),
665 DataType::Pointer(Integer {
666 sign: Sign::Unsigned,
667 width: BitWidth::Bit8,
668 }),
669 ]
670 );
671 }
672
673 fn data_file(name: &str) -> String {
674 format!("{}/test-data/{}", env!("CARGO_MANIFEST_DIR"), name)
675 }
676
677 #[test]
678 fn test_provider_struct() {
679 let provider_name = "foo";
680 let defn = std::fs::read_to_string(data_file(&format!("{}.d", provider_name))).unwrap();
681 let provider = Provider::try_from(
682 &DTraceParser::parse(Rule::FILE, &defn)
683 .unwrap()
684 .next()
685 .unwrap()
686 .into_inner(),
687 );
688 let provider = provider.unwrap();
689 assert_eq!(provider.name, provider_name);
690 assert_eq!(provider.probes.len(), 1);
691 assert_eq!(provider.probes[0].name, "baz");
692 }
693
694 #[test]
695 fn test_file_struct() {
696 let defn = r#"
697 /* a comment */
698 #pragma do stuff
699 provider foo {
700 probe quux();
701 probe quack(char*, uint16_t, uint8_t);
702 };
703 provider bar {
704 probe bar();
705 probe baz(char*, uint16_t, uint8_t);
706 };
707 "#;
708 let file = File::try_from(&DTraceParser::parse(Rule::FILE, defn).unwrap()).unwrap();
709 assert_eq!(file.providers.len(), 2);
710 assert_eq!(file.providers[0].name, "foo");
711 assert_eq!(file.providers[1].probes[1].name, "baz");
712
713 let file2 = File::try_from(defn).unwrap();
714 assert_eq!(file, file2);
715
716 assert!(File::try_from("this is not a D file").is_err());
717 }
718}