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