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