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