1 extern crate cargo_metadata;
2 extern crate semver;
3 #[macro_use]
4 extern crate serde_json;
5 
6 use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand};
7 use std::path::PathBuf;
8 
9 #[test]
old_minimal()10 fn old_minimal() {
11     // Output from oldest supported version (1.24).
12     // This intentionally has as many null fields as possible.
13     // 1.8 is when metadata was introduced.
14     // Older versions not supported because the following are required:
15     // - `workspace_members` added in 1.13
16     // - `target_directory` added in 1.19
17     // - `workspace_root` added in 1.24
18     let json = r#"
19 {
20   "packages": [
21     {
22       "name": "foo",
23       "version": "0.1.0",
24       "id": "foo 0.1.0 (path+file:///foo)",
25       "license": null,
26       "license_file": null,
27       "description": null,
28       "source": null,
29       "dependencies": [
30         {
31           "name": "somedep",
32           "source": null,
33           "req": "^1.0",
34           "kind": null,
35           "optional": false,
36           "uses_default_features": true,
37           "features": [],
38           "target": null
39         }
40       ],
41       "targets": [
42         {
43           "kind": [
44             "bin"
45           ],
46           "crate_types": [
47             "bin"
48           ],
49           "name": "foo",
50           "src_path": "/foo/src/main.rs"
51         }
52       ],
53       "features": {},
54       "manifest_path": "/foo/Cargo.toml"
55     }
56   ],
57   "workspace_members": [
58     "foo 0.1.0 (path+file:///foo)"
59   ],
60   "resolve": null,
61   "target_directory": "/foo/target",
62   "version": 1,
63   "workspace_root": "/foo"
64 }
65 "#;
66     let meta: Metadata = serde_json::from_str(json).unwrap();
67     assert_eq!(meta.packages.len(), 1);
68     let pkg = &meta.packages[0];
69     assert_eq!(pkg.name, "foo");
70     assert_eq!(pkg.version, semver::Version::parse("0.1.0").unwrap());
71     assert_eq!(pkg.authors.len(), 0);
72     assert_eq!(pkg.id.to_string(), "foo 0.1.0 (path+file:///foo)");
73     assert_eq!(pkg.description, None);
74     assert_eq!(pkg.license, None);
75     assert_eq!(pkg.license_file, None);
76     assert_eq!(pkg.dependencies.len(), 1);
77     let dep = &pkg.dependencies[0];
78     assert_eq!(dep.name, "somedep");
79     assert_eq!(dep.source, None);
80     assert_eq!(dep.req, semver::VersionReq::parse("^1.0").unwrap());
81     assert_eq!(dep.kind, DependencyKind::Normal);
82     assert_eq!(dep.optional, false);
83     assert_eq!(dep.uses_default_features, true);
84     assert_eq!(dep.features.len(), 0);
85     assert!(dep.target.is_none());
86     assert_eq!(dep.rename, None);
87     assert_eq!(dep.registry, None);
88     assert_eq!(pkg.targets.len(), 1);
89     let target = &pkg.targets[0];
90     assert_eq!(target.name, "foo");
91     assert_eq!(target.kind, vec!["bin"]);
92     assert_eq!(target.crate_types, vec!["bin"]);
93     assert_eq!(target.required_features.len(), 0);
94     assert_eq!(target.src_path, PathBuf::from("/foo/src/main.rs"));
95     assert_eq!(target.edition, "2015");
96     assert_eq!(target.doctest, true);
97     assert_eq!(target.test, true);
98     assert_eq!(pkg.features.len(), 0);
99     assert_eq!(pkg.manifest_path, PathBuf::from("/foo/Cargo.toml"));
100     assert_eq!(pkg.categories.len(), 0);
101     assert_eq!(pkg.keywords.len(), 0);
102     assert_eq!(pkg.readme, None);
103     assert_eq!(pkg.repository, None);
104     assert_eq!(pkg.homepage, None);
105     assert_eq!(pkg.documentation, None);
106     assert_eq!(pkg.edition, "2015");
107     assert_eq!(pkg.metadata, serde_json::Value::Null);
108     assert_eq!(pkg.links, None);
109     assert_eq!(pkg.publish, None);
110     assert_eq!(meta.workspace_members.len(), 1);
111     assert_eq!(
112         meta.workspace_members[0].to_string(),
113         "foo 0.1.0 (path+file:///foo)"
114     );
115     assert!(meta.resolve.is_none());
116     assert_eq!(meta.workspace_root, PathBuf::from("/foo"));
117     assert_eq!(meta.workspace_metadata, serde_json::Value::Null);
118     assert_eq!(meta.target_directory, PathBuf::from("/foo/target"));
119 }
120 
121 macro_rules! sorted {
122     ($e:expr) => {{
123         let mut v = $e.clone();
124         v.sort();
125         v
126     }};
127 }
128 
cargo_version() -> semver::Version129 fn cargo_version() -> semver::Version {
130     let output = std::process::Command::new("cargo")
131         .arg("-V")
132         .output()
133         .expect("Failed to exec cargo.");
134     let out = std::str::from_utf8(&output.stdout)
135         .expect("invalid utf8")
136         .trim();
137     let split: Vec<&str> = out.split_whitespace().collect();
138     assert!(split.len() >= 2, "cargo -V output is unexpected: {}", out);
139     let mut ver = semver::Version::parse(split[1]).expect("cargo -V semver could not be parsed");
140     // Don't care about metadata, it is awkward to compare.
141     ver.pre = Vec::new();
142     ver.build = Vec::new();
143     ver
144 }
145 
146 #[derive(serde::Deserialize, PartialEq, Eq, Debug)]
147 struct WorkspaceMetadata {
148     testobject: TestObject,
149 }
150 
151 #[derive(serde::Deserialize, PartialEq, Eq, Debug)]
152 struct TestObject {
153     myvalue: String,
154 }
155 
156 #[test]
all_the_fields()157 fn all_the_fields() {
158     // All the fields currently generated as of 1.49. This tries to exercise as
159     // much as possible.
160     let ver = cargo_version();
161     let minimum = semver::Version::parse("1.49.0").unwrap();
162     if ver < minimum {
163         // edition added in 1.30
164         // rename added in 1.31
165         // links added in 1.33
166         // doctest added in 1.37
167         // publish added in 1.39
168         // dep_kinds added in 1.41
169         // test added in 1.47
170         // homepage added in 1.49
171         // documentation added in 1.49
172         // path added in 1.51
173         eprintln!("Skipping all_the_fields test, cargo {} is too old.", ver);
174         return;
175     }
176     let meta = MetadataCommand::new()
177         .manifest_path("tests/all/Cargo.toml")
178         .exec()
179         .unwrap();
180     assert_eq!(
181         meta.workspace_root.file_name().unwrap(),
182         PathBuf::from("all")
183     );
184     assert_eq!(
185         serde_json::from_value::<WorkspaceMetadata>(meta.workspace_metadata).unwrap(),
186         WorkspaceMetadata {
187             testobject: TestObject {
188                 myvalue: "abc".to_string()
189             }
190         }
191     );
192     assert_eq!(meta.workspace_members.len(), 1);
193     assert!(meta.workspace_members[0].to_string().starts_with("all"));
194 
195     assert_eq!(meta.packages.len(), 9);
196     let all = meta.packages.iter().find(|p| p.name == "all").unwrap();
197     assert_eq!(all.version, semver::Version::parse("0.1.0").unwrap());
198     assert_eq!(all.authors, vec!["Jane Doe <user@example.com>"]);
199     assert!(all.id.to_string().starts_with("all"));
200     assert_eq!(all.description, Some("Package description.".to_string()));
201     assert_eq!(all.license, Some("MIT/Apache-2.0".to_string()));
202     assert_eq!(all.license_file, Some(PathBuf::from("LICENSE")));
203     assert!(all.license_file().unwrap().ends_with("tests/all/LICENSE"));
204     assert_eq!(all.publish, Some(vec![]));
205     assert_eq!(all.links, Some("foo".to_string()));
206 
207     assert_eq!(all.dependencies.len(), 8);
208     let bitflags = all
209         .dependencies
210         .iter()
211         .find(|d| d.name == "bitflags")
212         .unwrap();
213     assert_eq!(
214         bitflags.source,
215         Some("registry+https://github.com/rust-lang/crates.io-index".to_string())
216     );
217     assert_eq!(bitflags.optional, true);
218     assert_eq!(bitflags.req, semver::VersionReq::parse("^1.0").unwrap());
219 
220     let path_dep = all
221         .dependencies
222         .iter()
223         .find(|d| d.name == "path-dep")
224         .unwrap();
225     assert_eq!(path_dep.source, None);
226     assert_eq!(path_dep.kind, DependencyKind::Normal);
227     assert_eq!(path_dep.req, semver::VersionReq::parse("*").unwrap());
228     if ver >= semver::Version::parse("1.51.0").unwrap() {
229         assert_eq!(
230             path_dep.path.as_ref().map(|p| p.ends_with("path-dep")),
231             Some(true),
232         );
233     }
234 
235     all.dependencies
236         .iter()
237         .find(|d| d.name == "namedep")
238         .unwrap();
239 
240     let featdep = all
241         .dependencies
242         .iter()
243         .find(|d| d.name == "featdep")
244         .unwrap();
245     assert_eq!(featdep.features, vec!["i128"]);
246     assert_eq!(featdep.uses_default_features, false);
247 
248     let renamed = all
249         .dependencies
250         .iter()
251         .find(|d| d.name == "oldname")
252         .unwrap();
253     assert_eq!(renamed.rename, Some("newname".to_string()));
254 
255     let devdep = all
256         .dependencies
257         .iter()
258         .find(|d| d.name == "devdep")
259         .unwrap();
260     assert_eq!(devdep.kind, DependencyKind::Development);
261 
262     let bdep = all.dependencies.iter().find(|d| d.name == "bdep").unwrap();
263     assert_eq!(bdep.kind, DependencyKind::Build);
264 
265     let windep = all
266         .dependencies
267         .iter()
268         .find(|d| d.name == "windep")
269         .unwrap();
270     assert_eq!(
271         windep.target.as_ref().map(|x| x.to_string()),
272         Some("cfg(windows)".to_string())
273     );
274 
275     macro_rules! get_file_name {
276         ($v:expr) => {
277             all.targets
278                 .iter()
279                 .find(|t| t.src_path.file_name().unwrap() == $v)
280                 .unwrap()
281         };
282     }
283     assert_eq!(all.targets.len(), 8);
284     let lib = get_file_name!("lib.rs");
285     assert_eq!(lib.name, "all");
286     assert_eq!(
287         sorted!(lib.kind),
288         vec!["cdylib", "dylib", "rlib", "staticlib"]
289     );
290     assert_eq!(
291         sorted!(lib.crate_types),
292         vec!["cdylib", "dylib", "rlib", "staticlib"]
293     );
294     assert_eq!(lib.required_features.len(), 0);
295     assert_eq!(lib.edition, "2018");
296     assert_eq!(lib.doctest, true);
297     assert_eq!(lib.test, true);
298 
299     let main = get_file_name!("main.rs");
300     assert_eq!(main.crate_types, vec!["bin"]);
301     assert_eq!(main.kind, vec!["bin"]);
302     assert_eq!(main.doctest, false);
303     assert_eq!(main.test, true);
304 
305     let otherbin = get_file_name!("otherbin.rs");
306     assert_eq!(otherbin.edition, "2015");
307 
308     let reqfeat = get_file_name!("reqfeat.rs");
309     assert_eq!(reqfeat.required_features, vec!["feat2"]);
310 
311     let ex1 = get_file_name!("ex1.rs");
312     assert_eq!(ex1.kind, vec!["example"]);
313     assert_eq!(ex1.test, false);
314 
315     let t1 = get_file_name!("t1.rs");
316     assert_eq!(t1.kind, vec!["test"]);
317 
318     let b1 = get_file_name!("b1.rs");
319     assert_eq!(b1.kind, vec!["bench"]);
320 
321     let build = get_file_name!("build.rs");
322     assert_eq!(build.kind, vec!["custom-build"]);
323 
324     assert_eq!(all.features.len(), 3);
325     assert_eq!(all.features["feat1"].len(), 0);
326     assert_eq!(all.features["feat2"].len(), 0);
327     assert_eq!(sorted!(all.features["default"]), vec!["bitflags", "feat1"]);
328 
329     assert!(all.manifest_path.ends_with("all/Cargo.toml"));
330     assert_eq!(all.categories, vec!["command-line-utilities"]);
331     assert_eq!(all.keywords, vec!["cli"]);
332     assert_eq!(all.readme, Some(PathBuf::from("README.md")));
333     assert_eq!(
334         all.repository,
335         Some("https://github.com/oli-obk/cargo_metadata/".to_string())
336     );
337     assert_eq!(
338         all.homepage,
339         Some("https://github.com/oli-obk/cargo_metadata/".to_string())
340     );
341     assert_eq!(
342         all.documentation,
343         Some("https://docs.rs/cargo_metadata/".to_string())
344     );
345     assert_eq!(all.edition, "2018");
346     assert_eq!(
347         all.metadata,
348         json!({
349             "docs": {
350                 "rs": {
351                     "all-features": true,
352                     "default-target": "x86_64-unknown-linux-gnu",
353                     "rustc-args": ["--example-rustc-arg"]
354                 }
355             }
356         })
357     );
358 
359     let resolve = meta.resolve.as_ref().unwrap();
360     assert!(resolve
361         .root
362         .as_ref()
363         .unwrap()
364         .to_string()
365         .starts_with("all"));
366 
367     assert_eq!(resolve.nodes.len(), 9);
368     let path_dep = resolve
369         .nodes
370         .iter()
371         .find(|n| n.id.to_string().starts_with("path-dep"))
372         .unwrap();
373     assert_eq!(path_dep.deps.len(), 0);
374     assert_eq!(path_dep.dependencies.len(), 0);
375     assert_eq!(path_dep.features.len(), 0);
376 
377     let bitflags = resolve
378         .nodes
379         .iter()
380         .find(|n| n.id.to_string().starts_with("bitflags"))
381         .unwrap();
382     assert_eq!(bitflags.features, vec!["default"]);
383 
384     let featdep = resolve
385         .nodes
386         .iter()
387         .find(|n| n.id.to_string().starts_with("featdep"))
388         .unwrap();
389     assert_eq!(featdep.features, vec!["i128"]);
390 
391     let all = resolve
392         .nodes
393         .iter()
394         .find(|n| n.id.to_string().starts_with("all"))
395         .unwrap();
396     assert_eq!(all.dependencies.len(), 8);
397     assert_eq!(all.deps.len(), 8);
398     let newname = all.deps.iter().find(|d| d.name == "newname").unwrap();
399     assert!(newname.pkg.to_string().starts_with("oldname"));
400     // Note the underscore here.
401     let path_dep = all.deps.iter().find(|d| d.name == "path_dep").unwrap();
402     assert!(path_dep.pkg.to_string().starts_with("path-dep"));
403     assert_eq!(path_dep.dep_kinds.len(), 1);
404     let kind = &path_dep.dep_kinds[0];
405     assert_eq!(kind.kind, DependencyKind::Normal);
406     assert!(kind.target.is_none());
407 
408     let namedep = all
409         .deps
410         .iter()
411         .find(|d| d.name == "different_name")
412         .unwrap();
413     assert!(namedep.pkg.to_string().starts_with("namedep"));
414     assert_eq!(sorted!(all.features), vec!["bitflags", "default", "feat1"]);
415 
416     let bdep = all.deps.iter().find(|d| d.name == "bdep").unwrap();
417     assert_eq!(bdep.dep_kinds.len(), 1);
418     let kind = &bdep.dep_kinds[0];
419     assert_eq!(kind.kind, DependencyKind::Build);
420     assert!(kind.target.is_none());
421 
422     let devdep = all.deps.iter().find(|d| d.name == "devdep").unwrap();
423     assert_eq!(devdep.dep_kinds.len(), 1);
424     let kind = &devdep.dep_kinds[0];
425     assert_eq!(kind.kind, DependencyKind::Development);
426     assert!(kind.target.is_none());
427 
428     let windep = all.deps.iter().find(|d| d.name == "windep").unwrap();
429     assert_eq!(windep.dep_kinds.len(), 1);
430     let kind = &windep.dep_kinds[0];
431     assert_eq!(kind.kind, DependencyKind::Normal);
432     assert_eq!(
433         kind.target.as_ref().map(|x| x.to_string()),
434         Some("cfg(windows)".to_string())
435     );
436 }
437 
438 #[test]
alt_registry()439 fn alt_registry() {
440     // This is difficult to test (would need to set up a custom index).
441     // Just manually check the JSON is handled.
442     let json = r#"
443 {
444   "packages": [
445     {
446       "name": "alt",
447       "version": "0.1.0",
448       "id": "alt 0.1.0 (path+file:///alt)",
449       "source": null,
450       "dependencies": [
451         {
452           "name": "alt2",
453           "source": "registry+https://example.com",
454           "req": "^0.1",
455           "kind": null,
456           "rename": null,
457           "optional": false,
458           "uses_default_features": true,
459           "features": [],
460           "target": null,
461           "registry": "https://example.com"
462         }
463       ],
464       "targets": [
465         {
466           "kind": [
467             "lib"
468           ],
469           "crate_types": [
470             "lib"
471           ],
472           "name": "alt",
473           "src_path": "/alt/src/lib.rs",
474           "edition": "2018"
475         }
476       ],
477       "features": {},
478       "manifest_path": "/alt/Cargo.toml",
479       "metadata": null,
480       "authors": [],
481       "categories": [],
482       "keywords": [],
483       "readme": null,
484       "repository": null,
485       "edition": "2018",
486       "links": null
487     }
488   ],
489   "workspace_members": [
490     "alt 0.1.0 (path+file:///alt)"
491   ],
492   "resolve": null,
493   "target_directory": "/alt/target",
494   "version": 1,
495   "workspace_root": "/alt"
496 }
497 "#;
498     let meta: Metadata = serde_json::from_str(json).unwrap();
499     assert_eq!(meta.packages.len(), 1);
500     let alt = &meta.packages[0];
501     let deps = &alt.dependencies;
502     assert_eq!(deps.len(), 1);
503     let dep = &deps[0];
504     assert_eq!(dep.registry, Some("https://example.com".to_string()));
505 }
506 
507 #[test]
current_dir()508 fn current_dir() {
509     let meta = MetadataCommand::new()
510         .current_dir("tests/all/namedep")
511         .exec()
512         .unwrap();
513     let namedep = meta.packages.iter().find(|p| p.name == "namedep").unwrap();
514     assert!(namedep.name.starts_with("namedep"));
515 }
516 
517 #[test]
parse_stream_is_robust()518 fn parse_stream_is_robust() {
519     // Proc macros can print stuff to stdout, which naturally breaks JSON messages.
520     // Let's check that we don't die horribly in this case, and report an error.
521     let json_output = r##"{"reason":"compiler-artifact","package_id":"chatty 0.1.0 (path+file:///chatty-macro/chatty)","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"chatty","src_path":"/chatty-macro/chatty/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/deps/libchatty-f2adcff24cdf3bb2.so"],"executable":null,"fresh":false}
522 Evil proc macro was here!
523 {"reason":"compiler-artifact","package_id":"chatty-macro 0.1.0 (path+file:///chatty-macro)","target":{"kind":["lib"],"crate_types":["lib"],"name":"chatty-macro","src_path":"/chatty-macro/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/libchatty_macro.rlib","/chatty-macro/target/debug/deps/libchatty_macro-cb5956ed52a11fb6.rmeta"],"executable":null,"fresh":false}
524 "##;
525     let mut n_messages = 0;
526     let mut text = String::new();
527     for message in cargo_metadata::Message::parse_stream(json_output.as_bytes()) {
528         let message = message.unwrap();
529         match message {
530             cargo_metadata::Message::TextLine(line) => text = line,
531             _ => n_messages += 1,
532         }
533     }
534     assert_eq!(n_messages, 2);
535     assert_eq!(text, "Evil proc macro was here!");
536 }
537 
538 #[test]
advanced_feature_configuration()539 fn advanced_feature_configuration() {
540     fn build_features<F: FnOnce(&mut MetadataCommand) -> &mut MetadataCommand>(
541         func: F,
542     ) -> Vec<String> {
543         let mut meta = MetadataCommand::new();
544         let meta = meta.manifest_path("tests/all/Cargo.toml");
545 
546         let meta = func(meta);
547         let meta = meta.exec().unwrap();
548 
549         let resolve = meta.resolve.as_ref().unwrap();
550 
551         let all = resolve
552             .nodes
553             .iter()
554             .find(|n| n.id.to_string().starts_with("all"))
555             .unwrap();
556 
557         all.features.clone()
558     }
559 
560     // Default behavior; tested above
561     let default_features = build_features(|meta| meta);
562     assert_eq!(
563         sorted!(default_features),
564         vec!["bitflags", "default", "feat1"]
565     );
566 
567     // Manually specify the same default features
568     let manual_features = build_features(|meta| {
569         meta.features(CargoOpt::NoDefaultFeatures)
570             .features(CargoOpt::SomeFeatures(vec![
571                 "feat1".into(),
572                 "bitflags".into(),
573             ]))
574     });
575     assert_eq!(sorted!(manual_features), vec!["bitflags", "feat1"]);
576 
577     // Multiple SomeFeatures is same as one longer SomeFeatures
578     let manual_features = build_features(|meta| {
579         meta.features(CargoOpt::NoDefaultFeatures)
580             .features(CargoOpt::SomeFeatures(vec!["feat1".into()]))
581             .features(CargoOpt::SomeFeatures(vec!["feat2".into()]))
582     });
583     assert_eq!(sorted!(manual_features), vec!["feat1", "feat2"]);
584 
585     // No features + All features == All features
586     let all_features = build_features(|meta| {
587         meta.features(CargoOpt::AllFeatures)
588             .features(CargoOpt::NoDefaultFeatures)
589     });
590     assert_eq!(
591         sorted!(all_features),
592         vec!["bitflags", "default", "feat1", "feat2"]
593     );
594 
595     // The '--all-features' flag supersedes other feature flags
596     let all_flag_variants = build_features(|meta| {
597         meta.features(CargoOpt::SomeFeatures(vec!["feat2".into()]))
598             .features(CargoOpt::NoDefaultFeatures)
599             .features(CargoOpt::AllFeatures)
600     });
601     assert_eq!(sorted!(all_flag_variants), sorted!(all_features));
602 }
603