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